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/
.lsp/
.nrepl-port
doc/scratch.clj

View file

@ -157,16 +157,18 @@
Throws an exception if parsing fails."
([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)
(map #(compile % return-tuple?) lines)
(let [src (trim rule-text)
parse-tree (simplify (parse src))
fn' (generate parse-tree)
(let [src (first lines)
parse-tree (doall (simplify (parse src)))
fn' (doall (generate parse-tree))
afn (try
(if (= 'fn (first fn'))
(if (#{'fn 'fn*} (first 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)
{:src src
:parse parse-tree

View file

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

View file

@ -3,7 +3,7 @@
[mw-engine.core :refer [transform-world]]
[mw-engine.utils :refer [get-cell]]
[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?]]))
(deftest rules-tests
@ -34,21 +34,20 @@
(deftest exception-tests
(testing "Constructions which should cause exceptions to be thrown"
(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")
(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")
(is (thrown-with-msg?
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'")
(is (thrown-with-msg?
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'")))
(deftest correctness-tests
;; 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.

View file

@ -1,69 +1,43 @@
(ns mw-parser.flow-test
(:require ;; [clojure.pprint :as pprint]
[clojure.test :refer [deftest is testing]] ;; [mw-engine.core :refer [transform-world]]
[mw-parser.declarative :refer [parse]]
[clojure.test :refer [deftest is testing]] ;; [mw-engine.core :refer [transform-world]]
[mw-parser.declarative :refer [parse]]
[mw-parser.simplify :refer [simplify]]))
(deftest parse-flow-tests
(testing "flow-grammar"
(let [rule "flow 1 food from house having food more than 10 to house within 2 with least food"
expected '(:FLOW-RULE
(:FLOW "flow")
(:QUANTITY (:SIMPLE-EXPRESSION (:NUMBER "1")))
(:SYMBOL "food")
(:FROM "from")
(: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")))
(:TO-HOW (:TO "to"))
(:DESTINATION
(:TARGET
(:PROPERTY-CONDITION (:SYMBOL "state") (:QUALIFIER (:EQUIVALENCE (:IS "is"))) (:EXPRESSION (:VALUE [:SYMBOL "house"])))
(:RANGE (:WITHIN "within") (:NUMBER "2")))
(:WITH "with")
(:DETERMINER-CONDITION (:DETERMINER (:LEAST "least")) (:SYMBOL "food"))))
actual (simplify (parse rule))]
(is (= actual expected) rule))
(let [rule "flow 10% food from house having food more than 10 to each house within 2 with food less than 4"
expected '(:FLOW-RULE
(:SIMPLE-EXPRESSION (:NUMBER "1"))
(:FLOW "flow")
(:QUANTITY (:PERCENTAGE (:NUMBER "10") "%"))
(:SYMBOL "food")
(:FROM "from")
(: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")))
(:TO-HOW (:TO "to"))
(: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")))
(:TO-HOW (:TO-EACH (:TO "to") (:EACH "each")))
(:DESTINATION
(:PROPERTY-CONDITION
(:SYMBOL "state")
(:QUALIFIER (:EQUIVALENCE (:IS "is")))
(:EXPRESSION (:VALUE [:SYMBOL "house"])))
(:WITHIN "within")
(:VALUE (:NUMBER "2"))
(:WITH "with")
(:FLOW-CONDITIONS
(:DETERMINER-CONDITION (:LEAST "least") (:SYMBOL "food")))))
(:TARGET (:PROPERTY-CONDITION (:SYMBOL "state") (:QUALIFIER (:EQUIVALENCE (:IS "is"))) (:EXPRESSION (:VALUE [:SYMBOL "house"])))
(:RANGE (:WITHIN "within") (:NUMBER "2")))
(:WITH "with") (:PROPERTY-CONDITION (:SYMBOL "food") (:QUALIFIER (:COMPARATIVE-QUALIFIER (:LESS "less") (:THAN "than"))) (:NUMBER "4"))))
actual (simplify (parse rule))]
(is (= actual expected) rule))
(let [rule "flow 10% food from house having food more than 10 to each house within 2 with food less than 4"
expected '(:FLOW-RULE
(:FLOW "flow")
(:QUANTITY (:PERCENTAGE (:NUMBER "10") "%"))
(:SYMBOL "food")
(:FROM "from")
(: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")))
(:TO-HOW (:TO-EACH (:TO "to") (:EACH "each")))
(:DESTINATION
(:TARGET
(:PROPERTY-CONDITION
(:SYMBOL "state") (:QUALIFIER (:EQUIVALENCE (:IS "is")))
(:EXPRESSION (:VALUE [:SYMBOL "house"])))
(:RANGE (:WITHIN "within") (:NUMBER "2")))
(:WITH "with")
(:PROPERTY-CONDITION
(:SYMBOL "food")
(:QUALIFIER
(:COMPARATIVE-QUALIFIER (:LESS "less") (:THAN "than")))
(:NUMBER "4"))))
actual (simplify (parse rule))]
(is (= actual expected) rule))))
(is (= actual expected) rule))))

View file

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