Much hacking on rule language, getting it to support initial behaviour.

This commit is contained in:
Simon Brooke 2014-07-13 20:27:14 +01:00
parent e15068ec2e
commit ef3ec6cf18
2 changed files with 75 additions and 61 deletions

View file

@ -5,7 +5,7 @@ A rule parser for MicroWorld
## Usage ## Usage
Main entry point is (parse-rule _string_), where string takes a form detailed Main entry point is (parse-rule _string_), where string takes a form detailed
in __grammar__, below. If the rule is interpretted correctly the result will in __[grammar](#grammar)__, below. If the rule is interpretted correctly the result will
be a Clojure anonymous function; if the rule is not interpretted, currently nil be a Clojure anonymous function; if the rule is not interpretted, currently nil
is returned and there's no helpful error message. is returned and there's no helpful error message.
@ -23,7 +23,7 @@ properties which are special and SHOULD NOT be modified, namely the properties
__x__ and __y__. Currently there is no policing that these properties are not __x__ and __y__. Currently there is no policing that these properties are not
modified. modified.
### Grammar ### <a name="grammar"></a>Grammar
A rule comprises: A rule comprises:

View file

@ -47,29 +47,34 @@
;; sequence of tokens (and in some cases other optional arguments) and return a ;; sequence of tokens (and in some cases other optional arguments) and return a
;; vector comprising ;; vector comprising
;; ;;
;; # A code fragment parsed from the front of the sequence of tokens, and ;; 1. A code fragment parsed from the front of the sequence of tokens, and
;; # the remaining tokens which were not consumed in constructing that sequence. ;; 2. the remaining tokens which were not consumed in constructing that fragment.
;; ;;
;; In every case if the function cannot parse the desired construct from the ;; In every case if the function cannot parse the desired construct from the
;; front of the sequence of tokens it returns nil. ;; front of the sequence of tokens it returns nil.
(defn- parse-numeric-value (defn parse-numeric-value
"Parse a number." "Parse a number."
[[value & remainder]] [[value & remainder]]
(if (re-matches re-number value) [(read-string value) remainder])) (if (re-matches re-number value) [(read-string value) remainder]))
(defn- parse-property-int (defn parse-property-int
"Parse a token assumed to be the name of a property of the current cell, "Parse a token assumed to be the name of a property of the current cell,
whose value is assumed to be an integer." whose value is assumed to be an integer."
[[value & remainder]] [[value & remainder]]
(if value [(list 'get-int 'cell (keyword value)) remainder])) (if value [(list 'get-int 'cell (keyword value)) remainder]))
(defn- parse-property-value (defn parse-property-value
"Parse a token assumed to be the name of a property of the current cell." "Parse a token assumed to be the name of a property of the current cell."
[[value & remainder]] [[value & remainder]]
(if value [(list (keyword value) 'cell) remainder])) (if value [(list (keyword value) 'cell) remainder]))
(defn- parse-simple-value (defn parse-token-value
"Parse a token assumed to be a simple token value."
[[value & remainder]]
(if value [(keyword value) remainder]))
(defn parse-simple-value
"Parse a value from the first of these `tokens`. If `expect-int` is true, return "Parse a value from the first of these `tokens`. If `expect-int` is true, return
an integer or something which will evaluate to an integer." an integer or something which will evaluate to an integer."
([tokens expect-int] ([tokens expect-int]
@ -77,25 +82,24 @@
(parse-numeric-value tokens) (parse-numeric-value tokens)
(cond expect-int (cond expect-int
(parse-property-int tokens) (parse-property-int tokens)
true (parse-property-value tokens)))) true (parse-token-value tokens))))
([tokens] ([tokens]
(parse-simple-value tokens false))) (parse-simple-value tokens false)))
(defn- parse-disjunct-value (defn parse-disjunct-value
"Parse a list of values from among these `tokens`. If `expect-int` is true, return "Parse a list of values from among these `tokens`. If `expect-int` is true, return
an integer or something which will evaluate to an integer." integers or things which will evaluate to integers."
[[OR token & tokens] expect-int] [[OR token & tokens] expect-int]
(cond (member? OR '("or" "in")) (cond (member? OR '("or" "in"))
(let [[others remainder] (parse-disjunct-value tokens expect-int)] (let [value (first (parse-simple-value (list token) expect-int))
[(cons seek-others (= (first tokens) "or")]
(cond (cond seek-others
expect-int (first (parse-simple-value (list token) true)) (let [[others remainder] (parse-disjunct-value tokens expect-int)]
true (keyword token)) [(cons value others) remainder])
others) true
remainder]) [(list value) tokens]))))
true [nil (cons OR (cons token tokens))]))
(defn- parse-value (defn parse-value
"Parse a value from among these `tokens`. If `expect-int` is true, return "Parse a value from among these `tokens`. If `expect-int` is true, return
an integer or something which will evaluate to an integer." an integer or something which will evaluate to an integer."
([tokens expect-int] ([tokens expect-int]
@ -107,26 +111,28 @@
(defn- parse-member-condition (defn- parse-member-condition
"Parses a condition of the form '[property] in [value] or [value]...'" "Parses a condition of the form '[property] in [value] or [value]...'"
[[property IN & rest]] [[property IS IN & rest]]
(if (= IN "in") (if (and (member? IS '("is" "are")) (= IN "in"))
(let [[l remainder] (parse-disjunct-value (cons "in" rest) false)] (let [[l remainder] (parse-disjunct-value (cons "in" rest) false)]
[(list 'member? (keyword property) l) remainder]))) [(list 'member? (keyword property) l) remainder])))
(defn- parse-less-condition (defn- parse-less-condition
"Parse '[property] less than [value]'." "Parse '[property] less than [value]'."
[[property LESS THAN value & rest]] [[property IS LESS THAN & rest]]
(cond (and (= LESS "less") (= THAN "than")) (cond (and (member? IS '("is" "are")) (member? LESS '("less" "fewer")) (= THAN "than"))
[(list '< (list 'get-int 'cell (keyword property)) (read-string value)) rest])) (let [[value remainder] (parse-value rest true)]
[(list '< (list 'get-int 'cell (keyword property)) value) remainder])))
(defn- parse-more-condition (defn- parse-more-condition
"Parse '[property] more than [value]'." "Parse '[property] more than [value]'."
[[property MORE THAN value & rest]] [[property IS MORE THAN & rest]]
(cond (and (= MORE "more") (= THAN "than")) (cond (and (member? IS '("is" "are")) (member? MORE '("more" "greater")) (= THAN "than"))
[(list '> (list 'get-int 'cell (keyword property)) (read-string value)) rest])) (let [[value remainder] (parse-value rest true)]
[(list '> (list 'get-int 'cell (keyword property)) value) remainder])))
(defn- parse-between-condition (defn- parse-between-condition
[[p BETWEEN v1 AND v2 & rest]] [[p IS BETWEEN v1 AND v2 & rest]]
(cond (and (= BETWEEN "between") (= AND "and") (not (nil? v2))) (cond (and (member? IS '("is" "are")) (= BETWEEN "between") (= AND "and") (not (nil? v2)))
(let [property (first (parse-simple-value (list p) true)) (let [property (first (parse-simple-value (list p) true))
value1 (first (parse-simple-value (list v1) true)) value1 (first (parse-simple-value (list v1) true))
value2 (first (parse-simple-value (list v2) true))] value2 (first (parse-simple-value (list v2) true))]
@ -143,10 +149,6 @@
(member? IS '("is" "are")) (member? IS '("is" "are"))
(let [tokens (cons property (cons value rest))] (let [tokens (cons property (cons value rest))]
(cond (cond
(= value "in") (parse-member-condition tokens)
(= value "between") (parse-between-condition tokens)
(= value "more") (parse-more-condition tokens)
(= value "less") (parse-less-condition tokens)
(re-matches re-number value) [(list '= (list 'get-int 'cell (keyword property)) (read-string value)) rest] (re-matches re-number value) [(list '= (list 'get-int 'cell (keyword property)) (read-string value)) rest]
value [(list '= (list (keyword property) 'cell) (keyword value)) rest])))) value [(list '= (list (keyword property) 'cell) (keyword value)) rest]))))
@ -160,15 +162,15 @@
[(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 comp2]
[(list comparator [(list comparator
(list 'count (list 'count
(list 'get-neighbours-with-property-value 'world 'cell (list 'get-neighbours-with-property-value 'world '(cell :x) '(cell :y)
(keyword property) (keyword-or-numeric value))) (keyword property) (keyword-or-numeric value) comp2))
quantity) quantity)
remainder]) remainder])
(defn- parse-comparator-neighbours-condition (defn parse-comparator-neighbours-condition
"Parse conditions of the form '...more than 6 neighbours are [condition]'" "Parse conditions of the form '...more than 6 neighbours are [condition]'"
[[MORE THAN n NEIGHBOURS have-or-are & rest]] [[MORE THAN n NEIGHBOURS have-or-are & rest]]
(let [quantity (first (parse-numeric-value (list n))) (let [quantity (first (parse-numeric-value (list n)))
@ -182,24 +184,24 @@
(cond (cond
(= have-or-are "are") (= have-or-are "are")
(let [[value & remainder] rest] (let [[value & remainder] rest]
(gen-neighbours-condition comparator quantity :state value remainder)) (gen-neighbours-condition comparator quantity :state value remainder =))
(= have-or-are "have") (= have-or-are "have")
(let [[property comp1 comp2 value & remainder] rest] (let [[property comp1 comp2 value & remainder] rest]
(cond (and (= comp1 "equal") (= comp2 "to")) (cond (and (= comp1 "equal") (= comp2 "to"))
(gen-neighbours-condition comparator quantity property value remainder) (gen-neighbours-condition comparator quantity property value remainder =)
;; (and (= comp1 "more") (= comp2 "than")) (and (= comp1 "more") (= comp2 "than"))
;; (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-some-neighbours-condition (defn parse-some-neighbours-condition
[[SOME NEIGHBOURS & rest]] [[SOME NEIGHBOURS & rest]]
(cond (cond
(and (= SOME "some") (= NEIGHBOURS "neighbours")) (and (= SOME "some") (= NEIGHBOURS "neighbours"))
(parse-comparator-neighbours-condition (concat '("more" "than" "0" "neighbours") rest)))) (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'" "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)))]
@ -219,7 +221,7 @@
;; (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
@ -228,16 +230,17 @@
(parse-some-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-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-less-condition tokens) (parse-less-condition tokens)
(parse-more-condition tokens))) (parse-more-condition tokens)
(parse-between-condition tokens)
(parse-is-condition tokens)))
(defn- parse-disjunction-condition (defn- parse-disjunction-condition
"Parse '... or [condition]' from `tokens`, where `left` is the already parsed first disjunct." "Parse '... or [condition]' from `tokens`, where `left` is the already parsed first disjunct."
@ -281,10 +284,11 @@
(= be "be") (= be "be")
(member? operator '("+" "-" "*" "/"))) (member? operator '("+" "-" "*" "/")))
[(list 'merge (or previous 'cell) [(list 'merge (or previous 'cell)
{(keyword prop1) (list (symbol operator) (list 'get-int 'cell (keyword prop2)) {(keyword prop1) (list 'int
(cond (list (symbol operator) (list 'get-int 'cell (keyword prop2))
(re-matches re-number value) (read-string value) (cond
true (list 'get-int 'cell (keyword value))))}) rest])) (re-matches re-number value) (read-string value)
true (list 'get-int 'cell (keyword value)))))}) rest]))
(defn- parse-set-action (defn- parse-set-action
"Parse actions of the form '[property] should be [value].'" "Parse actions of the form '[property] should be [value].'"
@ -329,21 +333,31 @@
(parse-actions nil tokens)))) (parse-actions nil tokens))))
(defn parse-rule (defn parse-rule
"Parse a complete rule from this string or sequence of string tokens." "Parse a complete rule from this `line`, expected to be either a string or a
sequence of string tokens. Return the rule in the form of an S-expression.
Throws an exception if parsing fails."
[line] [line]
(cond (cond
(string? line) (parse-rule (split (triml line) #"\s+")) (string? line)
true (let [[left remainder] (parse-left-hand-side line) (let [rule (parse-rule (split (triml line) #"\s+"))]
(cond rule rule
true (throw (Exception. (str "I did not understand '" line "'")))))
true
(let [[left remainder] (parse-left-hand-side line)
[right junk] (parse-right-hand-side remainder)] [right junk] (parse-right-hand-side remainder)]
;; there shouldn't be any junk (should be null) ;; TODO: there shouldn't be any junk (should be null)
(list 'fn ['cell 'world] (list 'if left right)) (cond
))) (and left right (nil? junk))
(list 'fn ['cell 'world] (list 'if left right))))))
(defn compile-rule (defn compile-rule
"Parse this `rule-text`, a string conforming to the grammar of MicroWorld rules, "Parse this `rule-text`, a string conforming to the grammar of MicroWorld rules,
into Clojure source, and then compile it into an anonymous into Clojure source, and then compile it into an anonymous
function object, getting round the problem of binding mw-engine.utils in function object, getting round the problem of binding mw-engine.utils in
the compiling environment." the compiling environment.
Throws an exception if parsing fails."
[rule-text] [rule-text]
(do (do
(use 'mw-engine.utils) (use 'mw-engine.utils)