Skip to content

Commit 0d6a8ae

Browse files
committed
Merge remote-tracking branch 'upstream/main'
2 parents 0854469 + 327eef5 commit 0d6a8ae

File tree

6 files changed

+78
-76
lines changed

6 files changed

+78
-76
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ figwheel_server.log
1818
.clj-kondo
1919
.lsp
2020
.cpcache
21+
node_modules/*

CHANGELOG.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,38 +75,44 @@ This is a history of changes to k13labs/clara-rules.
7575

7676
This is a history of changes to clara-rules prior to forking to k13labs/clara-rules.
7777

78-
# 0.23.0
78+
### 0.24.0
79+
* uplift to cljs 1.11.132
80+
* uplift to clj 1.11.2
81+
* remove atom usage in LHS functions
82+
* remove redundant TestNode evaluations
83+
84+
### 0.23.0
7985
* extract clara.rules.compiler/compile-test-handler from clara.rules.compiler/compile-test
8086
* add support for `env` inside of test expressions
8187
* use `.clj_kondo` extension for clj-kondo hook code for better tool compatibility (clj-kondo support now requires clj-kondo 2022.04.25 or higher)
8288
* Include the invalid constraint in the exception thrown at session compilation time when negations have multiple children. See [Issue 284](https://github.com/cerner/clara-rules/issues/284).
8389

84-
# 0.22.1
90+
### 0.22.1
8591
* fix incorrent lint warning triggered when this binding is not used in clj-kondo hooks
8692

87-
# 0.22.0
93+
### 0.22.0
8894
* add built-in clj-kondo support for clara-rules as hooks. Importing should be automatic if using clojure-lsp; for detailed instructions see clj-kondo's documentation on [how to import clj-kondo configuration](https://github.com/clj-kondo/clj-kondo/blob/master/doc/config.md#importing)
8995
* use correct arity calling `->RuleOrderedActivation` constructor during serialization if clara session; this change should have the same effective behavior as before.
9096

91-
# 0.21.2
97+
### 0.21.2
9298
* Try and catch TestNode expression evaluation so that exceptions thrown are re-thrown wrapped in a condition exception which includes production name and bindings information. See [PR 471](https://github.com/cerner/clara-rules/pull/471).
9399

94-
# 0.21.1
100+
### 0.21.1
95101
* Add support to specify query binding arguments as symbols instead of only keywords so that defquery syntax looks closer to function definition syntax. See [PR 463](https://github.com/cerner/clara-rules/pull/463).
96102

97-
# 0.21.0
103+
### 0.21.0
98104
* Add names to anonymous functions generated by rule compilation; these names will be in the class names of the generated objects. [Issue 261](https://github.com/cerner/clara-rules/issues/261) and [issue 291](https://github.com/cerner/clara-rules/issues/291)
99105
* Add types information to alpha nodes. [Issue 237](https://github.com/cerner/clara-rules/issues/237)
100106
* Fix a bug related to Java object facts with IndexedPropertyDescriptor fields. [Issue 446](https://github.com/cerner/clara-rules/issues/446)
101107
* Validate that parameters provided to queries exist on the query at compilation and throw an exception if queries on a session don't specify the required parameters. [Issue 454](https://github.com/cerner/clara-rules/issues/454)
102108
* Add an optional listener that reports suspected infinite loops of rules. [Issue 275](https://github.com/cerner/clara-rules/issues/275)
103109

104-
# 0.20.0
110+
### 0.20.0
105111
* Add a flag to omit compilation context (used by the durability layer) after Session compilation to save space when not needed. Defaults to true. [issue 422](https://github.com/cerner/clara-rules/issues/422)
106112
* Correct duplicate bindings within the same condition. See [issue 417](https://github.com/cerner/clara-rules/issues/417)
107113
* Correct sharing of nodes with different parents. See [issue 433](https://github.com/cerner/clara-rules/issues/433)
108114

109-
# 0.19.1
115+
### 0.19.1
110116
* Added a new field to the clara.rules.engine/Accumulator record. This could be a breaking change for any user durability implementations with low-level performance optimizations. See [PR 410](https://github.com/cerner/clara-rules/pull/410) for details.
111117
* Performance improvements for :exists conditions. See [issue 298](https://github.com/cerner/clara-rules/issues/298).
112118
* Decrease memory usage post deserialization (Durability). See [Issue 419](https://github.com/cerner/clara-rules/issues/419)

src/main/clojure/clara/rules/compiler.clj

Lines changed: 49 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@
219219
([exp-seq equality-only-variables]
220220

221221
(if (empty? exp-seq)
222-
`(deref ~'?__bindings__)
222+
'?__bindings__
223223
(let [[exp & rest-exp] exp-seq
224224
variables (into #{}
225225
(filter (fn [item]
@@ -256,11 +256,11 @@
256256
;; First assign each value in a let, so it is visible to subsequent expressions.
257257
`(let [~@(for [variable variables
258258
let-expression [variable (first expression-values)]]
259-
let-expression)]
260-
261-
;; Update the bindings produced by this expression.
262-
~@(for [variable variables]
263-
`(swap! ~'?__bindings__ assoc ~(keyword variable) ~variable))
259+
let-expression)
260+
;; Update the bindings produced by this expression.
261+
;; intentional shadowing here of the ?__bindings__ variable with each newly
262+
;; bound variables associated.
263+
~'?__bindings__ (assoc ~'?__bindings__ ~@(mapcat (juxt keyword identity) variables))]
264264

265265
;; If there is more than one expression value, we need to ensure they are
266266
;; equal as well as doing the bind. This ensures that value-1 and value-2 are
@@ -371,18 +371,11 @@
371371
`(fn ~fn-name [~(add-meta '?__fact__ type)
372372
~destructured-env]
373373
(let [~@assignments
374-
~'?__bindings__ (atom ~initial-bindings)]
374+
~'?__bindings__ ~initial-bindings]
375375
~(compile-constraints constraints)))))
376376

377-
(defn build-token-assignment
378-
"A helper function to build variable assignment forms for tokens."
379-
[binding-key]
380-
(list (symbol (name binding-key))
381-
(list `-> '?__token__ :bindings binding-key)))
382-
383377
(defn compile-test-handler [node-id constraints env]
384378
(let [binding-keys (variables-as-keywords constraints)
385-
assignments (mapcat build-token-assignment binding-keys)
386379

387380
;; The destructured environment, if any
388381
destructured-env (if (> (count env) 0)
@@ -392,38 +385,42 @@
392385
;; Hardcoding the node-type and fn-type as we would only ever expect 'compile-test' to be used for this scenario
393386
fn-name (mk-node-fn-name "TestNode" node-id "TE")]
394387
`(fn ~fn-name [~'?__token__ ~destructured-env]
395-
(let [~@assignments]
396-
(and ~@constraints)))))
388+
;; exceedingly unlikely that we'd have a test node without bound variables to be tested,
389+
;; however since the contract is that of arbitrary clojure there is nothing preventing users
390+
;; from defining tests that look outside the Session here. In such event, those without bound variables,
391+
;; we can avoid the bindings entirely.
392+
~(if (seq binding-keys)
393+
`(let [{:keys [~@(map (comp symbol name) binding-keys)]} (:bindings ~'?__token__)]
394+
(and ~@constraints))
395+
`(and ~@constraints)))))
397396

398397
(defn compile-test [node-id constraints env]
399-
(let [test-handler (compile-test-handler node-id constraints env)]
400-
`(array-map :handler ~test-handler
401-
:constraints '~constraints)))
398+
(compile-test-handler node-id constraints env))
402399

403400
(defn compile-action-handler
404-
[action-name bindings-keys rhs env]
401+
[action-name binding-keys rhs env]
405402
(let [;; Avoid creating let bindings in the compile code that aren't actually used in the body.
406403
;; The bindings only exist in the scope of the RHS body, not in any code called by it,
407404
;; so this scanning strategy will detect all possible uses of binding variables in the RHS.
408405
;; Note that some strategies with macros could introduce bindings, but these aren't something
409406
;; we're trying to support. If necessary a user could macroexpand their RHS code manually before
410407
;; providing it to Clara.
411408
rhs-bindings-used (variables-as-keywords rhs)
412-
413-
assignments (sequence
414-
(comp
415-
(filter rhs-bindings-used)
416-
(mapcat build-token-assignment))
417-
bindings-keys)
409+
token-binding-keys (sequence
410+
(filter rhs-bindings-used)
411+
binding-keys)
418412

419413
;; The destructured environment, if any.
420414
destructured-env (if (> (count env) 0)
421-
{:keys (mapv (comp symbol name) (keys env))}
415+
{:keys (mapv #(symbol (name %)) (keys env))}
422416
'?__env__)]
423-
`(fn ~action-name
424-
[~'?__token__ ~destructured-env]
425-
(let [~@assignments]
426-
~rhs))))
417+
`(fn ~action-name [~'?__token__ ~destructured-env]
418+
;; similar to test nodes, nothing in the contract of an RHS enforces that bound variables must be used.
419+
;; similarly we will not bind anything in this event, and thus the let block would be superfluous.
420+
~(if (seq token-binding-keys)
421+
`(let [{:keys [~@(map (comp symbol name) token-binding-keys)]} (:bindings ~'?__token__)]
422+
~rhs)
423+
rhs))))
427424

428425
(defn compile-action
429426
"Compile the right-hand-side action of a rule, returning a function to execute it."
@@ -448,14 +445,14 @@
448445

449446
(defn compile-join-filter
450447
"Compiles to a predicate function that ensures the given items can be unified. Returns a ready-to-eval
451-
function that accepts the following:
448+
function that accepts the following:
452449
453-
* a token from the parent node
454-
* the fact
455-
* a map of bindings from the fact, which was typically computed on the alpha side
456-
* an environment
450+
* a token from the parent node
451+
* the fact
452+
* a map of bindings from the fact, which was typically computed on the alpha side
453+
* an environment
457454
458-
The function created here returns truthy if the given fact satisfies the criteria."
455+
The function created here returns truthy if the given fact satisfies the criteria."
459456
[node-id node-type {:keys [type constraints args] :as unification-condition} ancestor-bindings element-bindings env]
460457
(let [accessors (field-name->accessors-used type constraints)
461458

@@ -479,17 +476,6 @@
479476
;; created element bindings for this condition removed.
480477
token-binding-keys (remove element-bindings (variables-as-keywords constraints))
481478

482-
token-assignments (mapcat build-token-assignment token-binding-keys)
483-
484-
new-binding-assignments (mapcat #(list (symbol (name %))
485-
(list 'get '?__element-bindings__ %))
486-
element-bindings)
487-
488-
assignments (concat
489-
fact-assignments
490-
token-assignments
491-
new-binding-assignments)
492-
493479
equality-only-variables (into #{} (for [binding ancestor-bindings]
494480
(symbol (name (keyword binding)))))
495481

@@ -500,8 +486,14 @@
500486
~(add-meta '?__fact__ type)
501487
~'?__element-bindings__
502488
~destructured-env]
503-
(let [~@assignments
504-
~'?__bindings__ (atom {})]
489+
(let [~@fact-assignments
490+
;; We should always have some form of bound variables here, however in the event that we ever didn't
491+
;; there would be no need to generate non-existent bindings.
492+
~@(when (seq element-bindings)
493+
[{:keys (mapv (comp symbol name) element-bindings)} '?__element-bindings__])
494+
~@(when (seq token-binding-keys)
495+
[{:keys (mapv (comp symbol name) token-binding-keys)} (list :bindings '?__token__)])
496+
~'?__bindings__ {}]
505497
~(compile-constraints constraints equality-only-variables)))))
506498

507499
(defn- expr-type [expression]
@@ -1613,7 +1605,7 @@
16131605

16141606
(sc/defn ^:private compile-node
16151607
"Compiles a given node description into a node usable in the network with the
1616-
given children."
1608+
given children."
16171609
[beta-node :- (sc/conditional
16181610
(comp #{:production :query} :node-type) schema/ProductionNode
16191611
:else schema/ConditionNode)
@@ -1677,6 +1669,7 @@
16771669
(eng/->TestNode
16781670
id
16791671
env
1672+
(:constraints condition)
16801673
(compiled-expr-fn id :test-expr)
16811674
children)
16821675

@@ -1696,10 +1689,10 @@
16961689
(if (:join-filter-expressions beta-node)
16971690
(eng/->AccumulateWithJoinFilterNode
16981691
id
1699-
;; Create an accumulator structure for use when examining the node or the tokens
1700-
;; it produces.
1692+
;; Create an accumulator structure for use when examining the node or the tokens
1693+
;; it produces.
17011694
{:accumulator (:accumulator beta-node)
1702-
;; Include the original filter expressions in the constraints for inspection tooling.
1695+
;; Include the original filter expressions in the constraints for inspection tooling.
17031696
:from (update-in condition [:constraints]
17041697
into (-> beta-node :join-filter-expressions :constraints))}
17051698
compiled-accum
@@ -1712,8 +1705,8 @@
17121705
;; All unification is based on equality, so just use the simple accumulate node.
17131706
(eng/->AccumulateNode
17141707
id
1715-
;; Create an accumulator structure for use when examining the node or the tokens
1716-
;; it produces.
1708+
;; Create an accumulator structure for use when examining the node or the tokens
1709+
;; it produces.
17171710
{:accumulator (:accumulator beta-node)
17181711
:from condition}
17191712
compiled-accum

src/main/clojure/clara/rules/engine.clj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -949,7 +949,7 @@
949949
;; The test node represents a Rete extension in which an arbitrary test condition is run
950950
;; against bindings from ancestor nodes. Since this node
951951
;; performs no joins it does not accept right activations or retractions.
952-
(defrecord TestNode [id env test children]
952+
(defrecord TestNode [id env constraints test children]
953953
ILeftActivate
954954
(left-activate [node join-bindings tokens memory transport listener]
955955
(l/left-activate! listener node tokens)
@@ -960,7 +960,7 @@
960960
children
961961
(platform/compute-for
962962
[token tokens]
963-
(test-node-match->Token node (:handler test) env token))))
963+
(test-node-match->Token node test env token))))
964964

965965
(left-retract [node join-bindings tokens memory transport listener]
966966
(l/left-retract! listener node tokens)
@@ -972,7 +972,7 @@
972972

973973
IConditionNode
974974
(get-condition-description [this]
975-
(into [:test] (:constraints test))))
975+
(into [:test] constraints)))
976976

977977
(defn- do-accumulate
978978
"Runs the actual accumulation. Returns the accumulated value."

src/main/clojure/clara/tools/testing_utils.clj

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,9 @@
7171
[node-id binding-keys rhs env]
7272
(let [rhs-bindings-used (com/variables-as-keywords rhs)
7373

74-
assignments (sequence
75-
(comp
76-
(filter rhs-bindings-used)
77-
(mapcat com/build-token-assignment))
78-
binding-keys)
74+
token-binding-keys (sequence
75+
(filter rhs-bindings-used)
76+
binding-keys)
7977

8078
;; The destructured environment, if any.
8179
destructured-env (if (> (count env) 0)
@@ -84,10 +82,14 @@
8482

8583
;; Hardcoding the node-type and fn-type as we would only ever expect 'compile-action' to be used for this scenario
8684
fn-name (com/mk-node-fn-name "ProductionNode" node-id "AE")]
87-
`(fn ~fn-name [~'?__token__ ~destructured-env]
85+
`(fn ~fn-name [~'?__token__ ~destructured-env]
86+
;; similar to test nodes, nothing in the contract of an RHS enforces that bound variables must be used.
87+
;; similarly we will not bind anything in this event, and thus the let block would be superfluous.
8888
(async
89-
(let [~@assignments]
90-
~rhs)))))
89+
~(if (seq token-binding-keys)
90+
`(let [{:keys [~@(map (comp symbol name) token-binding-keys)]} (:bindings ~'?__token__)]
91+
~rhs)
92+
rhs)))))
9193

9294
(defn test-fire-rules-async
9395
([session]

src/test/clojure/clara/test_compiler.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
(let [get-node-fns (fn [node]
4444
(condp instance? node
4545
AlphaNode [(:activation node)]
46-
TestNode [(-> node :test :handler)]
46+
TestNode [(:test node)]
4747
AccumulateNode []
4848
AccumulateWithJoinFilterNode [(:join-filter-fn node)]
4949
ProductionNode [(:rhs node)]

0 commit comments

Comments
 (0)