Tactical commit before experimenting with a major change.
This commit is contained in:
parent
4b721219bd
commit
8c2e44b42a
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -7,3 +7,5 @@ pom.xml
|
|||
.clj-kondo/
|
||||
.lsp/
|
||||
.nrepl-port
|
||||
|
||||
doc/scratch.clj
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}))
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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))))
|
||||
|
|
|
@ -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)]
|
||||
|
|
Loading…
Reference in a new issue