Tactical commit before experimenting with a major change.

This commit is contained in:
Simon Brooke 2023-07-19 09:19:18 +01:00
parent 4b721219bd
commit 8c2e44b42a
6 changed files with 81 additions and 89 deletions

2
.gitignore vendored
View file

@ -7,3 +7,5 @@ pom.xml
.clj-kondo/ .clj-kondo/
.lsp/ .lsp/
.nrepl-port .nrepl-port
doc/scratch.clj

View file

@ -157,16 +157,18 @@
Throws an exception if parsing fails." Throws an exception if parsing fails."
([rule-text return-tuple?] ([rule-text return-tuple?]
(let [lines (remove comment? (split-lines rule-text))] (let [lines (map trim (remove comment? (split-lines rule-text)))]
(if (> (count lines) 1) (if (> (count lines) 1)
(map #(compile % return-tuple?) lines) (map #(compile % return-tuple?) lines)
(let [src (trim rule-text) (let [src (first lines)
parse-tree (simplify (parse src)) parse-tree (doall (simplify (parse src)))
fn' (generate parse-tree) fn' (doall (generate parse-tree))
afn (try afn (try
(if (= 'fn (first fn')) (if (#{'fn 'fn*} (first fn'))
(vary-meta (eval fn') merge (meta fn')) (vary-meta (eval fn') merge (meta fn'))
(throw (Exception. (format "Parse of `%s` did not return a functionn" src)))) (throw (Exception.
(format "Parse of `%s` did not return a function: %s"
src fn'))))
(catch Exception any (throw (ex-info (.getMessage any) (catch Exception any (throw (ex-info (.getMessage any)
{:src src {:src src
:parse parse-tree :parse parse-tree

View file

@ -25,13 +25,20 @@
(declare generate generate-action) (declare generate generate-action)
;;; macros used in generated rules ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; production (if-then) rules ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn generate-rule (defn generate-rule
"From this `tree`, assumed to be a syntactically correct rule specification, "From this `tree`, assumed to be a syntactically correct rule specification,
generate and return the appropriate rule as a function of two arguments." generate and return the appropriate rule as a function of two arguments."
[tree] [tree]
(assert-type tree :RULE) (assert-type tree :RULE)
(vary-meta (vary-meta
(list 'fn ['cell 'world] (list 'when (generate (nth tree 2)) (generate (nth tree 3)))) ;; do macro-expansion here, because at least in theory I know what
;; macros are in scope here.
(macroexpand
(list 'fn ['cell 'world] (list 'when (generate (nth tree 2)) (generate (nth tree 3)))))
merge merge
{:rule-type {:rule-type
:production})) :production}))
@ -275,19 +282,27 @@
{:tree tree} {:tree tree}
x))))) x)))))
;;; Flow rules. A flow rule DOES NOT return a modified world; instead, it ;;; Flow rules. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; returns a PLAN to modify the world, in the form of a sequence of `flows`. ;;; A flow rule DOES NOT return a modified cell; instead, it
;;; It is only when the plan is executed that the world is modified. ;;; returns a PLAN to modify the world, in the form of a sequence of `flow`
;;; objects. See `mw-engine.flow`
;;; ;;;
;;; so we're looking at something like ;;; It is only when the plan is executed that the world is modified.
;;; (fn [cell world])
;;; (if (= (:state cell) (or (:house cell) :house))
(defn flow-rule (defn flow-rule
"Generate a flow rule for this `quantity` of this `property` from this "Generate a flow rule for this `quantity` of this `property` from this
`source` to this `destination`." `source` to this `destination`.
A flow rule **does not** return a modified cell; instead, it
returns a **plan** to modify the world, in the form of a sequence of
`flow` objects. See `mw-engine.flow`
It is only when the plan is executed that the world is modified."
[source property quantity-frag destinations] [source property quantity-frag destinations]
(vary-meta (vary-meta
;; do macro-expansion here, because at least in theory I know what
;; macros are in scope here.
(macroexpand
(list 'fn ['cell 'world] (list 'fn ['cell 'world]
(list 'when (list 'and source (list 'pos? (list 'cell property))) (list 'when (list 'and source (list 'pos? (list 'cell property)))
(list 'map (list 'map
@ -296,7 +311,7 @@
:destination (list 'select-keys 'd [:x :y]) :destination (list 'select-keys 'd [:x :y])
:property property :property property
:quantity quantity-frag}) :quantity quantity-frag})
destinations))) destinations))))
merge merge
{:rule-type {:rule-type
:flow})) :flow}))

View file

@ -3,7 +3,7 @@
[mw-engine.core :refer [transform-world]] [mw-engine.core :refer [transform-world]]
[mw-engine.utils :refer [get-cell]] [mw-engine.utils :refer [get-cell]]
[mw-engine.world :refer [make-world]] [mw-engine.world :refer [make-world]]
[mw-parser.declarative :refer [compile parse-rule]] [mw-parser.declarative :refer [compile parse parse-rule]]
[mw-parser.utils :refer [rule?]])) [mw-parser.utils :refer [rule?]]))
(deftest rules-tests (deftest rules-tests
@ -34,21 +34,20 @@
(deftest exception-tests (deftest exception-tests
(testing "Constructions which should cause exceptions to be thrown" (testing "Constructions which should cause exceptions to be thrown"
(is (thrown-with-msg? Exception #"^I did not understand.*" (is (thrown-with-msg? Exception #"^I did not understand.*"
(compile "the quick brown fox jumped over the lazy dog")) (parse "the quick brown fox jumped over the lazy dog"))
"Exception thrown if rule text does not match grammar") "Exception thrown if rule text does not match grammar")
(is (thrown-with-msg? Exception #"^I did not understand.*" (is (thrown-with-msg? Exception #"^I did not understand.*"
(compile "if i have a cat on my lap then everything is fine")) (parse "if i have a cat on my lap then everything is fine"))
"Exception thrown if rule text does not match grammar") "Exception thrown if rule text does not match grammar")
(is (thrown-with-msg? (is (thrown-with-msg?
Exception #"The properties 'x' and 'y' of a cell are reserved and should not be set in rule actions" Exception #"The properties 'x' and 'y' of a cell are reserved and should not be set in rule actions"
(compile "if state is new then x should be 0")) (parse "if state is new then x should be 0"))
"Exception thrown on attempt to set 'x'") "Exception thrown on attempt to set 'x'")
(is (thrown-with-msg? (is (thrown-with-msg?
Exception #"The properties 'x' and 'y' of a cell are reserved and should not be set in rule actions" Exception #"The properties 'x' and 'y' of a cell are reserved and should not be set in rule actions"
(compile "if state is new then y should be 0")) (parse "if state is new then y should be 0"))
"Exception thrown on attempt to set 'y'"))) "Exception thrown on attempt to set 'y'")))
(deftest correctness-tests (deftest correctness-tests
;; these are, in so far as possible, the same as the correctness-tests in core-tests - i.e., the two compilers ;; these are, in so far as possible, the same as the correctness-tests in core-tests - i.e., the two compilers
;; compile the same language. ;; compile the same language.

View file

@ -8,30 +8,22 @@
(testing "flow-grammar" (testing "flow-grammar"
(let [rule "flow 1 food from house having food more than 10 to house within 2 with least food" (let [rule "flow 1 food from house having food more than 10 to house within 2 with least food"
expected '(:FLOW-RULE expected '(:FLOW-RULE
(:SIMPLE-EXPRESSION (:NUMBER "1")) (:FLOW "flow")
(:QUANTITY (:SIMPLE-EXPRESSION (:NUMBER "1")))
(:SYMBOL "food") (:SYMBOL "food")
(:FROM "from") (:FROM "from")
(:SOURCE (:SOURCE
(:PROPERTY-CONDITION (:PROPERTY-CONDITION (:SYMBOL "state") (:QUALIFIER (:EQUIVALENCE (:IS "is"))) (:EXPRESSION (:VALUE [:SYMBOL "house"])))
(:SYMBOL "state")
(:QUALIFIER (:EQUIVALENCE (:IS "is")))
(:EXPRESSION (:VALUE [:SYMBOL "house"])))
(:WITH "having") (:WITH "having")
(:PROPERTY-CONDITION (:PROPERTY-CONDITION (:SYMBOL "food") (:QUALIFIER (:COMPARATIVE-QUALIFIER (:MORE "more") (:THAN "than"))) (:NUMBER "10")))
(:SYMBOL "food")
(:QUALIFIER (:COMPARATIVE-QUALIFIER (:MORE "more") (:THAN "than")))
(:NUMBER "10")))
(:TO-HOW (:TO "to")) (:TO-HOW (:TO "to"))
(:DESTINATION (:DESTINATION
(:PROPERTY-CONDITION (:TARGET
(:SYMBOL "state") (:PROPERTY-CONDITION (:SYMBOL "state") (:QUALIFIER (:EQUIVALENCE (:IS "is"))) (:EXPRESSION (:VALUE [:SYMBOL "house"])))
(:QUALIFIER (:EQUIVALENCE (:IS "is"))) (:RANGE (:WITHIN "within") (:NUMBER "2")))
(:EXPRESSION (:VALUE [:SYMBOL "house"])))
(:WITHIN "within")
(:VALUE (:NUMBER "2"))
(:WITH "with") (:WITH "with")
(:FLOW-CONDITIONS (:DETERMINER-CONDITION (:DETERMINER (:LEAST "least")) (:SYMBOL "food"))))
(:DETERMINER-CONDITION (:LEAST "least") (:SYMBOL "food")))))
actual (simplify (parse rule))] actual (simplify (parse rule))]
(is (= actual expected) rule)) (is (= actual expected) rule))
@ -41,29 +33,11 @@
(:QUANTITY (:PERCENTAGE (:NUMBER "10") "%")) (:QUANTITY (:PERCENTAGE (:NUMBER "10") "%"))
(:SYMBOL "food") (:SYMBOL "food")
(:FROM "from") (:FROM "from")
(:SOURCE (:SOURCE (:PROPERTY-CONDITION (:SYMBOL "state") (:QUALIFIER (:EQUIVALENCE (:IS "is"))) (:EXPRESSION (:VALUE [:SYMBOL "house"]))) (:WITH "having") (:PROPERTY-CONDITION (:SYMBOL "food") (:QUALIFIER (:COMPARATIVE-QUALIFIER (:MORE "more") (:THAN "than"))) (:NUMBER "10")))
(:PROPERTY-CONDITION
(:SYMBOL "state")
(:QUALIFIER (:EQUIVALENCE (:IS "is")))
(:EXPRESSION (:VALUE [:SYMBOL "house"])))
(:WITH "having")
(:PROPERTY-CONDITION
(:SYMBOL "food")
(:QUALIFIER
(:COMPARATIVE-QUALIFIER (:MORE "more") (:THAN "than")))
(:NUMBER "10")))
(:TO-HOW (:TO-EACH (:TO "to") (:EACH "each"))) (:TO-HOW (:TO-EACH (:TO "to") (:EACH "each")))
(:DESTINATION (:DESTINATION
(:TARGET (:TARGET (:PROPERTY-CONDITION (:SYMBOL "state") (:QUALIFIER (:EQUIVALENCE (:IS "is"))) (:EXPRESSION (:VALUE [:SYMBOL "house"])))
(:PROPERTY-CONDITION
(:SYMBOL "state") (:QUALIFIER (:EQUIVALENCE (:IS "is")))
(:EXPRESSION (:VALUE [:SYMBOL "house"])))
(:RANGE (:WITHIN "within") (:NUMBER "2"))) (:RANGE (:WITHIN "within") (:NUMBER "2")))
(:WITH "with") (:WITH "with") (:PROPERTY-CONDITION (:SYMBOL "food") (:QUALIFIER (:COMPARATIVE-QUALIFIER (:LESS "less") (:THAN "than"))) (:NUMBER "4"))))
(:PROPERTY-CONDITION
(:SYMBOL "food")
(:QUALIFIER
(:COMPARATIVE-QUALIFIER (:LESS "less") (:THAN "than")))
(:NUMBER "4"))))
actual (simplify (parse rule))] actual (simplify (parse rule))]
(is (= actual expected) rule)))) (is (= actual expected) rule))))

View file

@ -77,10 +77,10 @@
(:SYMBOL "state") (:SYMBOL "state")
(:BECOMES "should be") (:BECOMES "should be")
(:SYMBOL "climax")))) (:SYMBOL "climax"))))
expected '(fn [cell world] expected '(fn* ([cell world]
(when (when
(= (:state cell) (or (:forest cell) :forest)) (= (:state cell) (or (:forest cell) :forest))
(merge cell {:state :climax}))) (merge cell {:state :climax}))))
actual (generate rule) actual (generate rule)
expected-meta {:rule-type :production} expected-meta {:rule-type :production}
actual-meta (meta actual)] actual-meta (meta actual)]