Woohoo! Feature complete. All the language features I need to reimplement
the core rule set in the rule language now compile.
This commit is contained in:
parent
9fd29fab53
commit
0d3ca990c1
|
@ -9,14 +9,19 @@
|
||||||
;; * "if state is forest and fertility is between 55 and 75 then state should be climax"
|
;; * "if state is forest and fertility is between 55 and 75 then state should be climax"
|
||||||
;; * "if 6 neighbours have state equal to water then state should be village"
|
;; * "if 6 neighbours have state equal to water then state should be village"
|
||||||
;; * "if state is in grassland or pasture or heath and 4 neighbours are water then state should be village"
|
;; * "if state is in grassland or pasture or heath and 4 neighbours are water then state should be village"
|
||||||
;;
|
;; * "if state is forest or state is climax and some neighbours have state equal to fire then 3 in 5 chance that state should be fire"
|
||||||
;; It should also but does not yet parse rules of the form:
|
|
||||||
|
|
||||||
;; * "if state is forest or state is climax and some neighbours have state is fire then 3 in 5 chance that state should be fire"
|
|
||||||
;; * "if state is pasture and more than 3 neighbours have state equal to scrub then state should be scrub"
|
;; * "if state is pasture and more than 3 neighbours have state equal to scrub then state should be scrub"
|
||||||
;; *
|
;; *
|
||||||
;;
|
;;
|
||||||
;; it generates rules in the form expected by mw-engine.core
|
;; it generates rules in the form expected by `mw-engine.core`, q.v.
|
||||||
|
;;
|
||||||
|
;; It is, as I say, very simple; it generates a complete rule, or it fails completely, returning nil.
|
||||||
|
;; Very occasionally it generates a wrong rule - one which is not a correct translation of the rule
|
||||||
|
;; semantics - but that is buggy behaviour, which I'll try to fix over the next few weeks, not a
|
||||||
|
;; design fault.
|
||||||
|
;;
|
||||||
|
;; More significantly it does not generate useful error messages on failure. This is, I think, a much
|
||||||
|
;; more complex issue which I don't yet know how to address.
|
||||||
|
|
||||||
(ns mw-parser.core
|
(ns mw-parser.core
|
||||||
(:use mw-engine.utils
|
(:use mw-engine.utils
|
||||||
|
@ -153,15 +158,48 @@
|
||||||
(let [[condition remainder] partial]
|
(let [[condition remainder] partial]
|
||||||
[(list 'not condition) remainder])))))
|
[(list 'not condition) remainder])))))
|
||||||
|
|
||||||
(defn gen-neighbours-condition
|
(defn- gen-neighbours-condition
|
||||||
[comparator quantity property value remainder]
|
[comparator quantity property value remainder]
|
||||||
[(list comparator quantity
|
[(list comparator
|
||||||
(list 'count
|
(list 'count
|
||||||
(list 'get-neighbours-with-property-value 'world 'cell
|
(list 'get-neighbours-with-property-value 'world 'cell
|
||||||
(keyword property) (keyword-or-numeric value))))
|
(keyword property) (keyword-or-numeric value)))
|
||||||
|
quantity)
|
||||||
remainder])
|
remainder])
|
||||||
|
|
||||||
|
(defn parse-comparator-neighbours-condition
|
||||||
|
"Parse conditions of the form '...more than 6 neighbours are [condition]'"
|
||||||
|
[[MORE THAN n NEIGHBOURS have-or-are & rest]]
|
||||||
|
(let [quantity (first (parse-numeric-value (list n)))
|
||||||
|
comparator (cond (= MORE "more") '>
|
||||||
|
(member? MORE '("fewer" "less")) '<)]
|
||||||
|
(cond
|
||||||
|
(and quantity
|
||||||
|
comparator
|
||||||
|
(= THAN "than")
|
||||||
|
(= NEIGHBOURS "neighbours"))
|
||||||
|
(cond
|
||||||
|
(= have-or-are "are")
|
||||||
|
(let [[value & remainder] rest]
|
||||||
|
(gen-neighbours-condition comparator quantity :state value remainder))
|
||||||
|
(= have-or-are "have")
|
||||||
|
(let [[property comp1 comp2 value & remainder] rest]
|
||||||
|
(cond (and (= comp1 "equal") (= comp2 "to"))
|
||||||
|
(gen-neighbours-condition comparator quantity property value remainder)
|
||||||
|
;; (and (= comp1 "more") (= comp2 "than"))
|
||||||
|
;; (gen-neighbours-condition '> quantity property value remainder)
|
||||||
|
;; (and (= comp1 "less") (= comp2 "than"))
|
||||||
|
;; (gen-neighbours-condition '< quantity property value remainder)
|
||||||
|
))))))
|
||||||
|
|
||||||
|
(defn parse-some-neighbours-condition
|
||||||
|
[[SOME NEIGHBOURS & rest]]
|
||||||
|
(cond
|
||||||
|
(and (= SOME "some") (= NEIGHBOURS "neighbours"))
|
||||||
|
(parse-comparator-neighbours-condition (concat '("more" "than" "0" "neighbours") rest))))
|
||||||
|
|
||||||
(defn parse-simple-neighbours-condition
|
(defn parse-simple-neighbours-condition
|
||||||
|
"Parse conditions of the form '...6 neighbours are condition'"
|
||||||
[[n NEIGHBOURS have-or-are & rest]]
|
[[n NEIGHBOURS have-or-are & rest]]
|
||||||
(let [quantity (first (parse-numeric-value (list n)))]
|
(let [quantity (first (parse-numeric-value (list n)))]
|
||||||
(cond
|
(cond
|
||||||
|
@ -178,23 +216,22 @@
|
||||||
;; (gen-neighbours-condition '> quantity property value remainder)
|
;; (gen-neighbours-condition '> quantity property value remainder)
|
||||||
;; (and (= comp1 "less") (= comp2 "than"))
|
;; (and (= comp1 "less") (= comp2 "than"))
|
||||||
;; (gen-neighbours-condition '< quantity property value remainder)
|
;; (gen-neighbours-condition '< quantity property value remainder)
|
||||||
)
|
))))))
|
||||||
)))))
|
|
||||||
|
|
||||||
(defn parse-neighbours-condition
|
(defn parse-neighbours-condition
|
||||||
"Parse conditions referring to neighbours"
|
"Parse conditions referring to neighbours"
|
||||||
[tokens]
|
[tokens]
|
||||||
(or
|
(or
|
||||||
(parse-simple-neighbours-condition tokens)
|
(parse-simple-neighbours-condition tokens)
|
||||||
;; (parse-more-than-neighbours-condition tokens)
|
(parse-comparator-neighbours-condition tokens)
|
||||||
;; (parse-fewer-than-neighbours-condition tokens)
|
(parse-some-neighbours-condition tokens)
|
||||||
))
|
))
|
||||||
|
|
||||||
(defn parse-simple-condition
|
(defn parse-simple-condition
|
||||||
"Parse conditions of the form '[property] [comparison] [value]'."
|
"Parse conditions of the form '[property] [comparison] [value]'."
|
||||||
[tokens]
|
[tokens]
|
||||||
(or
|
(or
|
||||||
(parse-simple-neighbours-condition tokens)
|
(parse-neighbours-condition tokens)
|
||||||
(parse-member-condition tokens)
|
(parse-member-condition tokens)
|
||||||
(parse-not-condition tokens)
|
(parse-not-condition tokens)
|
||||||
(parse-is-condition tokens)
|
(parse-is-condition tokens)
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
(ns mw-parser.core-test
|
(ns mw-parser.core-test
|
||||||
(:use [mw-engine.utils :refer :all])
|
(:use mw-engine.core
|
||||||
|
mw-engine.utils
|
||||||
|
mw-engine.world)
|
||||||
(:require [clojure.test :refer :all]
|
(:require [clojure.test :refer :all]
|
||||||
[mw-parser.core :refer :all]))
|
[mw-parser.core :refer :all]))
|
||||||
|
|
||||||
|
|
||||||
(deftest rules-tests
|
(deftest rules-tests
|
||||||
(testing "Rule parser - does not test whether generated functions actually work, just that something is generated!"
|
(testing "Rule parser - does not test whether generated functions actually work, just that something is generated!"
|
||||||
(is (parse-rule "if altitude is less than 100 and state is forest then state should be climax and deer should be 3"))
|
|
||||||
(is (parse-rule "if altitude is less than 100 and state is forest then state should be climax and deer should be 3"))
|
(is (parse-rule "if altitude is less than 100 and state is forest then state should be climax and deer should be 3"))
|
||||||
(is (parse-rule "if altitude is 100 or fertility is 25 then state should be heath and fertility should be 24.3"))
|
(is (parse-rule "if altitude is 100 or fertility is 25 then state should be heath and fertility should be 24.3"))
|
||||||
(is (parse-rule "if altitude is 100 or fertility is 25 then state should be heath"))
|
(is (parse-rule "if altitude is 100 or fertility is 25 then state should be heath"))
|
||||||
|
@ -16,6 +17,8 @@
|
||||||
(is (parse-rule "if state is forest and fertility is between 55 and 75 then state should be climax"))
|
(is (parse-rule "if state is forest and fertility is between 55 and 75 then state should be climax"))
|
||||||
(is (parse-rule "if 6 neighbours have state equal to water then state should be village"))
|
(is (parse-rule "if 6 neighbours have state equal to water then state should be village"))
|
||||||
(is (parse-rule "if state is in grassland or pasture or heath and 4 neighbours are water then state should be village"))
|
(is (parse-rule "if state is in grassland or pasture or heath and 4 neighbours are water then state should be village"))
|
||||||
|
(is (parse-rule "if state is forest or state is climax and some neighbours have state is fire then 3 in 5 chance that state should be fire"))
|
||||||
|
(is (parse-rule "if state is pasture and more than 3 neighbours have state equal to scrub then state should be scrub"))
|
||||||
))
|
))
|
||||||
|
|
||||||
;; ideally should also test that the rule works, but I haven't worked out how
|
;; ideally should also test that the rule works, but I haven't worked out how
|
||||||
|
@ -30,6 +33,18 @@
|
||||||
|
|
||||||
(deftest correctness-tests
|
(deftest correctness-tests
|
||||||
(testing "Testing that generated code performs as expected."
|
(testing "Testing that generated code performs as expected."
|
||||||
(is (let [afn (compile-rule "if altitude is less than 100 and state is forest then state should be climax and deer should be 3")
|
(is (let [afn (compile-rule "if altitude is less than 100 and state is forest then state should be climax and deer should be 3")]
|
||||||
cell (apply afn (list {:state :forest :altitude 99} nil))]
|
(= (apply afn (list {:state :forest :altitude 99} nil))
|
||||||
(and (= (:state cell) :climax) (= (:deer cell) 3))))))
|
{:state :climax :altitude 99 :deer 3})))
|
||||||
|
(is (let [afn (compile-rule "if state is new and more than 3 neighbours have state equal to new then state should be scrub")]
|
||||||
|
(= (transform-world (make-world 3 3) (list afn))
|
||||||
|
'(({:x 0, :y 0, :state :new}
|
||||||
|
{:x 1, :y 0, :state :scrub}
|
||||||
|
{:x 2, :y 0, :state :new})
|
||||||
|
({:x 0, :y 1, :state :scrub}
|
||||||
|
{:x 1, :y 1, :state :scrub}
|
||||||
|
{:x 2, :y 1, :state :scrub})
|
||||||
|
({:x 0, :y 2, :state :new}
|
||||||
|
{:x 1, :y 2, :state :scrub}
|
||||||
|
{:x 2, :y 2, :state :new})))))
|
||||||
|
))
|
||||||
|
|
Loading…
Reference in a new issue