").append(n.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},n.expr.filters.animated=function(a){return n.grep(n.timers,function(b){return a===b.elem}).length};var dd=a.document.documentElement;function ed(a){return n.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}n.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=n.css(a,"position"),l=n(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=n.css(a,"top"),i=n.css(a,"left"),j=("absolute"===k||"fixed"===k)&&n.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),n.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},n.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){n.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,n.contains(b,e)?(typeof e.getBoundingClientRect!==L&&(d=e.getBoundingClientRect()),c=ed(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===n.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),n.nodeName(a[0],"html")||(c=a.offset()),c.top+=n.css(a[0],"borderTopWidth",!0),c.left+=n.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-n.css(d,"marginTop",!0),left:b.left-c.left-n.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||dd;while(a&&!n.nodeName(a,"html")&&"static"===n.css(a,"position"))a=a.offsetParent;return a||dd})}}),n.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);n.fn[a]=function(d){return W(this,function(a,d,e){var f=ed(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?n(f).scrollLeft():e,c?e:n(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),n.each(["top","left"],function(a,b){n.cssHooks[b]=Mb(l.pixelPosition,function(a,c){return c?(c=Kb(a,b),Ib.test(c)?n(a).position()[b]+"px":c):void 0})}),n.each({Height:"height",Width:"width"},function(a,b){n.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return W(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.size=function(){return this.length},n.fn.andSelf=n.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return n});var fd=a.jQuery,gd=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=gd),b&&a.jQuery===n&&(a.jQuery=fd),n},typeof b===L&&(a.jQuery=a.$=n),n});
diff --git a/docs/codox/js/page_effects.js b/docs/codox/js/page_effects.js
new file mode 100644
index 0000000..fdacbf8
--- /dev/null
+++ b/docs/codox/js/page_effects.js
@@ -0,0 +1,112 @@
+function visibleInParent(element) {
+ var position = $(element).position().top
+ return position > -50 && position < ($(element).offsetParent().height() - 50)
+}
+
+function hasFragment(link, fragment) {
+ return $(link).attr("href").indexOf("#" + fragment) != -1
+}
+
+function findLinkByFragment(elements, fragment) {
+ return $(elements).filter(function(i, e) { return hasFragment(e, fragment)}).first()
+}
+
+function scrollToCurrentVarLink(elements) {
+ var elements = $(elements);
+ var parent = elements.offsetParent();
+
+ if (elements.length == 0) return;
+
+ var top = elements.first().position().top;
+ var bottom = elements.last().position().top + elements.last().height();
+
+ if (top >= 0 && bottom <= parent.height()) return;
+
+ if (top < 0) {
+ parent.scrollTop(parent.scrollTop() + top);
+ }
+ else if (bottom > parent.height()) {
+ parent.scrollTop(parent.scrollTop() + bottom - parent.height());
+ }
+}
+
+function setCurrentVarLink() {
+ $('.secondary a').parent().removeClass('current')
+ $('.anchor').
+ filter(function(index) { return visibleInParent(this) }).
+ each(function(index, element) {
+ findLinkByFragment(".secondary a", element.id).
+ parent().
+ addClass('current')
+ });
+ scrollToCurrentVarLink('.secondary .current');
+}
+
+var hasStorage = (function() { try { return localStorage.getItem } catch(e) {} }())
+
+function scrollPositionId(element) {
+ var directory = window.location.href.replace(/[^\/]+\.html$/, '')
+ return 'scroll::' + $(element).attr('id') + '::' + directory
+}
+
+function storeScrollPosition(element) {
+ if (!hasStorage) return;
+ localStorage.setItem(scrollPositionId(element) + "::x", $(element).scrollLeft())
+ localStorage.setItem(scrollPositionId(element) + "::y", $(element).scrollTop())
+}
+
+function recallScrollPosition(element) {
+ if (!hasStorage) return;
+ $(element).scrollLeft(localStorage.getItem(scrollPositionId(element) + "::x"))
+ $(element).scrollTop(localStorage.getItem(scrollPositionId(element) + "::y"))
+}
+
+function persistScrollPosition(element) {
+ recallScrollPosition(element)
+ $(element).scroll(function() { storeScrollPosition(element) })
+}
+
+function sidebarContentWidth(element) {
+ var widths = $(element).find('.inner').map(function() { return $(this).innerWidth() })
+ return Math.max.apply(Math, widths)
+}
+
+function calculateSize(width, snap, margin, minimum) {
+ if (width == 0) {
+ return 0
+ }
+ else {
+ return Math.max(minimum, (Math.ceil(width / snap) * snap) + (margin * 2))
+ }
+}
+
+function resizeSidebars() {
+ var primaryWidth = sidebarContentWidth('.primary')
+ var secondaryWidth = 0
+
+ if ($('.secondary').length != 0) {
+ secondaryWidth = sidebarContentWidth('.secondary')
+ }
+
+ // snap to grid
+ primaryWidth = calculateSize(primaryWidth, 32, 13, 160)
+ secondaryWidth = calculateSize(secondaryWidth, 32, 13, 160)
+
+ $('.primary').css('width', primaryWidth)
+ $('.secondary').css('width', secondaryWidth).css('left', primaryWidth + 1)
+
+ if (secondaryWidth > 0) {
+ $('#content').css('left', primaryWidth + secondaryWidth + 2)
+ }
+ else {
+ $('#content').css('left', primaryWidth + 1)
+ }
+}
+
+$(window).ready(resizeSidebars)
+$(window).ready(setCurrentVarLink)
+$(window).ready(function() { persistScrollPosition('.primary')})
+$(window).ready(function() {
+ $('#content').scroll(setCurrentVarLink)
+ $(window).resize(setCurrentVarLink)
+})
diff --git a/docs/codox/mw-parser.bulk.html b/docs/codox/mw-parser.bulk.html
new file mode 100644
index 0000000..6cca8f1
--- /dev/null
+++ b/docs/codox/mw-parser.bulk.html
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/docs/codox/mw-parser.core.html b/docs/codox/mw-parser.core.html
new file mode 100644
index 0000000..6b3474a
--- /dev/null
+++ b/docs/codox/mw-parser.core.html
@@ -0,0 +1,25 @@
+
+
\ No newline at end of file
diff --git a/docs/codox/mw-parser.declarative.html b/docs/codox/mw-parser.declarative.html
new file mode 100644
index 0000000..8f12634
--- /dev/null
+++ b/docs/codox/mw-parser.declarative.html
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/docs/codox/mw-parser.errors.html b/docs/codox/mw-parser.errors.html
new file mode 100644
index 0000000..e7a743e
--- /dev/null
+++ b/docs/codox/mw-parser.errors.html
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/docs/codox/mw-parser.flow.html b/docs/codox/mw-parser.flow.html
new file mode 100644
index 0000000..b3c6c6e
--- /dev/null
+++ b/docs/codox/mw-parser.flow.html
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/docs/codox/mw-parser.generate.html b/docs/codox/mw-parser.generate.html
new file mode 100644
index 0000000..f85e733
--- /dev/null
+++ b/docs/codox/mw-parser.generate.html
@@ -0,0 +1,23 @@
+
+
\ No newline at end of file
diff --git a/docs/codox/mw-parser.simplify.html b/docs/codox/mw-parser.simplify.html
new file mode 100644
index 0000000..4cf8d3f
--- /dev/null
+++ b/docs/codox/mw-parser.simplify.html
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/docs/codox/mw-parser.utils.html b/docs/codox/mw-parser.utils.html
new file mode 100644
index 0000000..5035017
--- /dev/null
+++ b/docs/codox/mw-parser.utils.html
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/docs/uberdoc.html b/docs/uberdoc.html
deleted file mode 100644
index fb3bf73..0000000
--- a/docs/uberdoc.html
+++ /dev/null
@@ -1,3882 +0,0 @@
-
-
dependenciesorg.clojure/clojure |
| 1.8.0 | org.clojure/tools.trace |
| 0.7.9 | instaparse |
| 1.4.1 | mw-engine |
| 0.1.6-SNAPSHOT |
|
(this space intentionally left almost blank) |
| |
| |
A very simple parser which parses production rules.
- | (ns ^{:doc
- :author "Simon Brooke"}
- mw-parser.declarative
- (:require [instaparse.core :as insta]
- [clojure.string :refer [split trim triml]]
- [mw-parser.errors :as pe]
- [mw-parser.generate :as pg]
- [mw-parser.simplify :as ps]
- [mw-parser.utils :refer [rule?]])) |
mw-parser: a rule parser for MicroWorld.
-
-This program is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-USA.
-
-Copyright (C) 2014 Simon Brooke
- | |
- | (def grammar
- ;; in order to simplify translation into other natural languages, all
- ;; TOKENS within the parser should be unambiguous
- "RULE := IF SPACE CONDITIONS SPACE THEN SPACE ACTIONS;
- CONDITIONS := DISJUNCT-CONDITION | CONJUNCT-CONDITION | CONDITION ;
- DISJUNCT-CONDITION := CONDITION SPACE OR SPACE CONDITIONS;
- CONJUNCT-CONDITION := CONDITION SPACE AND SPACE CONDITIONS;
- CONDITION := WITHIN-CONDITION | NEIGHBOURS-CONDITION | PROPERTY-CONDITION;
- WITHIN-CONDITION := QUANTIFIER SPACE NEIGHBOURS SPACE WITHIN SPACE NUMBER SPACE IS SPACE PROPERTY-CONDITION-OR-EXPRESSION;
- NEIGHBOURS-CONDITION := QUANTIFIER SPACE NEIGHBOURS SPACE IS SPACE PROPERTY-CONDITION | QUALIFIER SPACE NEIGHBOURS-CONDITION;
- PROPERTY-CONDITION-OR-EXPRESSION := PROPERTY-CONDITION | EXPRESSION;
- PROPERTY-CONDITION := PROPERTY SPACE QUALIFIER SPACE EXPRESSION | VALUE;
- EXPRESSION := SIMPLE-EXPRESSION | RANGE-EXPRESSION | NUMERIC-EXPRESSION | DISJUNCT-EXPRESSION | VALUE;
- SIMPLE-EXPRESSION := QUALIFIER SPACE EXPRESSION | VALUE;
- DISJUNCT-EXPRESSION := IN SPACE DISJUNCT-VALUE;
- RANGE-EXPRESSION := BETWEEN SPACE NUMERIC-EXPRESSION SPACE AND SPACE NUMERIC-EXPRESSION;
- NUMERIC-EXPRESSION := VALUE | VALUE SPACE OPERATOR SPACE NUMERIC-EXPRESSION;
- NEGATED-QUALIFIER := QUALIFIER SPACE NOT | NOT SPACE QUALIFIER;
- COMPARATIVE-QUALIFIER := IS SPACE COMPARATIVE SPACE THAN | COMPARATIVE SPACE THAN;
- QUALIFIER := COMPARATIVE-QUALIFIER | NEGATED-QUALIFIER | EQUIVALENCE | IS SPACE QUALIFIER;
- QUANTIFIER := NUMBER | SOME | NONE | ALL | COMPARATIVE SPACE THAN SPACE NUMBER;
- EQUIVALENCE := IS SPACE EQUAL | EQUAL | IS ;
- COMPARATIVE := MORE | LESS;
- DISJUNCT-VALUE := VALUE | VALUE SPACE OR SPACE DISJUNCT-VALUE;
- IF := 'if';
- THEN := 'then';
- THAN := 'than';
- OR := 'or';
- NOT := 'not';
- AND := 'and';
- SOME := 'some';
- NONE := 'no';
- ALL := 'all'
- BETWEEN := 'between';
- WITHIN := 'within';
- IN := 'in';
- MORE := 'more' | 'greater';
- LESS := 'less' | 'fewer';
- OPERATOR := '+' | '-' | '*' | '/';
- NEIGHBOURS := 'neighbour' | 'neighbor' | 'neighbours' | 'neighbors';
- PROPERTY := SYMBOL;
- VALUE := SYMBOL | NUMBER;
- EQUAL := 'equal to';
- IS := 'is' | 'are' | 'have' | 'has';
- NUMBER := #'[0-9]+' | #'[0-9]+.[0-9]+';
- SYMBOL := #'[a-z]+';
- ACTIONS := ACTION | ACTION SPACE AND SPACE ACTIONS
- ACTION := SIMPLE-ACTION | PROBABLE-ACTION;
- PROBABLE-ACTION := VALUE SPACE CHANCE-IN SPACE VALUE SPACE SIMPLE-ACTION;
- SIMPLE-ACTION := SYMBOL SPACE BECOMES SPACE EXPRESSION;
- CHANCE-IN := 'chance in';
- BECOMES := 'should be' | 'becomes';
- SPACE := #' *'";) |
Parse the argument, assumed to be a string in the correct syntax, and return a parse tree.
- | (def parse-rule
- (insta/parser grammar)) |
Parse this rule-text , a string conforming to the grammar of MicroWorld rules,
- into Clojure source, and then compile it into an anonymous
- function object, getting round the problem of binding mw-engine.utils in
- the compiling environment. If return-tuple? is present and true, return
- a list comprising the anonymous function compiled, and the function from
- which it was compiled.
-
- Throws an exception if parsing fails.
- | (defn compile-rule
- ([rule-text return-tuple?]
- (assert (string? rule-text))
- (let [rule (trim rule-text)
- tree (ps/simplify (parse-rule rule))
- afn (if (rule? tree) (eval (pg/generate tree))
- ;; else
- (pe/throw-parse-exception tree))]
- (if return-tuple?
- (list afn rule)
- ;; else
- afn)))
- ([rule-text]
- (compile-rule rule-text false))) |
| |
| |
Generate Clojure source from simplified parse trees.
- | (ns ^{:doc
- :author "Simon Brooke"}
- mw-parser.generate
- (:require [mw-engine.utils :refer []]
- [mw-parser.utils :refer [assert-type TODO]]
- [mw-parser.errors :as pe])) |
This program is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-USA.
- | |
- | (declare generate generate-action) |
From this tree , assumed to be a syntactically correct rule specification,
- generate and return the appropriate rule as a function of two arguments.
- | (defn generate-rule
- [tree]
- (assert-type tree :RULE)
- (list 'fn ['cell 'world] (list 'if (generate (nth tree 2)) (generate (nth tree 3))))) |
From this tree , assumed to be a syntactically correct conditions clause,
- generate and return the appropriate clojure fragment.
- | (defn generate-conditions
- [tree]
- (assert-type tree :CONDITIONS)
- (generate (second tree))) |
From this tree , assumed to be a syntactically correct condition clause,
- generate and return the appropriate clojure fragment.
- | (defn generate-condition
- [tree]
- (assert-type tree :CONDITION)
- (generate (second tree))) |
- | (defn generate-conjunct-condition
- [tree]
- "From this `tree`, assumed to be a syntactically conjunct correct condition clause,
- generate and return the appropriate clojure fragment."
- (assert-type tree :CONJUNCT-CONDITION)
- (cons 'and (map generate (rest tree)))) |
From this tree , assumed to be a syntactically correct disjunct condition clause,
- generate and return the appropriate clojure fragment.
- | (defn generate-disjunct-condition
- [tree]
- (assert-type tree :DISJUNCT-CONDITION)
- (cons 'or (map generate (rest tree)))) |
From this tree , assumed to be a syntactically property condition clause for
- this property where the expression is a numeric range, generate and return
- the appropriate clojure fragment.
- | (defn generate-ranged-property-condition
- [tree property expression]
- (assert-type tree :PROPERTY-CONDITION)
- (assert-type (nth tree 3) :RANGE-EXPRESSION)
- (let [l1 (generate (nth expression 2))
- l2 (generate (nth expression 4))
- pv (list property 'cell)]
- (list 'let ['lower (list 'min l1 l2)
- 'upper (list 'max l1 l2)]
- (list 'and (list '>= pv 'lower)(list '<= pv 'upper))))) |
From this tree , assumed to be a syntactically property condition clause
- where the expression is a a disjunction, generate and return
- the appropriate clojure fragment.
- TODO: this is definitely still wrong!
- | (defn generate-disjunct-property-condition
- ([tree]
- (let [property (generate (second tree))
- qualifier (generate (nth tree 2))
- expression (generate (nth tree 3))]
- (generate-disjunct-property-condition tree property qualifier expression)))
- ([tree property qualifier expression]
- (let [e (list 'some (list 'fn ['i] '(= i value)) (list 'quote expression))]
- (list 'let ['value (list property 'cell)]
- (if (= qualifier '=) e
- (list 'not e)))))) |
From this tree , assumed to be a syntactically property condition clause,
- generate and return the appropriate clojure fragment.
- | (defn generate-property-condition
- ([tree]
- (assert-type tree :PROPERTY-CONDITION)
- (if
- (and (= (count tree) 2) (= (first (second tree)) :SYMBOL))
- ;; it's a shorthand for 'state equal to symbol'. This should probably have
- ;; been handled in simplify...
- (generate-property-condition
- (list
- :PROPERTY-CONDITION
- '(:SYMBOL "state")
- '(:QUALIFIER (:EQUIVALENCE (:EQUAL "equal to")))
- (second tree)))
- ;; otherwise...
- (generate-property-condition tree (first (nth tree 3)))))
- ([tree expression-type]
- (assert-type tree :PROPERTY-CONDITION)
- (let [property (generate (second tree))
- qualifier (generate (nth tree 2))
- e (generate (nth tree 3))
- expression (cond
- (and (not (= qualifier '=)) (keyword? e)) (list 'or (list e 'cell) e)
- (and (not (= qualifier 'not=)) (keyword? e)) (list 'or (list e 'cell) e)
- :else e)]
- (case expression-type
- :DISJUNCT-EXPRESSION (generate-disjunct-property-condition tree property qualifier expression)
- :RANGE-EXPRESSION (generate-ranged-property-condition tree property expression)
- (list qualifier (list property 'cell) expression))))) |
From this tree , assumed to be a syntactically correct qualifier,
- generate and return the appropriate clojure fragment.
- | (defn generate-qualifier
- [tree]
- (if
- (= (count tree) 2)
- (generate (second tree))
- ;; else
- (generate (nth tree 2)))) |
From this tree , assumed to be a syntactically correct simple action,
- generate and return the appropriate clojure fragment.
- | (defn generate-simple-action
- ([tree]
- (assert-type tree :SIMPLE-ACTION)
- (generate-simple-action tree []))
- ([tree others]
- (assert-type tree :SIMPLE-ACTION)
- (let [property (generate (second tree))
- expression (generate (nth tree 3))]
- (if (or (= property :x) (= property :y))
- (throw (Exception. pe/reserved-properties-error))
- (list 'merge
- (if (empty? others) 'cell
- ;; else
- (generate others))
- {property expression}))))) |
From this tree , assumed to be a syntactically correct probable action,
- generate and return the appropriate clojure fragment.
- | (defn generate-probable-action
- ([tree]
- (assert-type tree :PROBABLE-ACTION)
- (generate-probable-action tree []))
- ([tree others]
- (assert-type tree :PROBABLE-ACTION)
- (let
- [chances (generate (nth tree 1))
- total (generate (nth tree 2))
- action (generate-action (nth tree 3) others)]
- ;; TODO: could almost certainly be done better with macro syntax
- (list 'if
- (list '< (list 'rand total) chances)
- action)))) |
From this tree , assumed to be a syntactically correct action,
- generate and return the appropriate clojure fragment.
- | (defn generate-action
- [tree others]
- (case (first tree)
- :ACTIONS (generate-action (first tree) others)
- :SIMPLE-ACTION (generate-simple-action tree others)
- :PROBABLE-ACTION (generate-probable-action tree others)
- (throw (Exception. (str "Not a known action type: " (first tree)))))) |
From this tree , assumed to be one or more syntactically correct actions,
- generate and return the appropriate clojure fragment.
- | (defn generate-multiple-actions
- [tree]
- (assert-type tree :ACTIONS)
- (generate-action (first (rest tree)) (second (rest tree)))) |
Generate a disjunct value. Essentially what we need here is to generate a
- flat list of values, since the member has already been taken care of.
- | (defn generate-disjunct-value
- [tree]
- (assert-type tree :DISJUNCT-VALUE)
- (if (= (count tree) 4)
- (cons (generate (second tree)) (generate (nth tree 3)))
- (list (generate (second tree))))) |
From this tree , assumed to be a syntactically correct numeric expression,
- generate and return the appropriate clojure fragment.
- | (defn generate-numeric-expression
- [tree]
- (assert-type tree :NUMERIC-EXPRESSION)
- (case (count tree)
- 4 (let [[p operator expression] (rest tree)
- property (if (number? p) p (list p 'cell))]
- (list (generate operator) (generate property) (generate expression)))
- (case (first (second tree))
- :SYMBOL (list (keyword (second (second tree))) 'cell)
- (generate (second tree))))) |
Generate code for a condition which refers to neighbours.
- | (defn generate-neighbours-condition
- ([tree]
- (assert-type tree :NEIGHBOURS-CONDITION)
- (case (first (second tree))
- :NUMBER (read-string (second (second tree)))
- :QUANTIFIER (generate-neighbours-condition tree (first (second (second tree))))
- :QUALIFIER (cons (generate (second tree)) (rest (generate (nth tree 2))))))
- ([tree quantifier-type]
- (let [quantifier (second tree)
- pc (generate (nth tree 4))]
- (case quantifier-type
- :NUMBER (generate-neighbours-condition '= (read-string (second (second quantifier))) pc 1)
- :SOME (generate-neighbours-condition '> 0 pc 1)
- :MORE (let [value (generate (nth quantifier 3))]
- (generate-neighbours-condition '> value pc 1))
- :LESS (let [value (generate (nth quantifier 3))]
- (generate-neighbours-condition '< value pc 1)))))
- ([comp1 quantity property-condition distance]
- (list comp1
- (list 'count
- (list 'remove 'false?
- (list 'map (list 'fn ['cell] property-condition)
- (list 'mw-engine.utils/get-neighbours 'world 'cell distance)))) quantity))
- ([comp1 quantity property-condition]
- (generate-neighbours-condition comp1 quantity property-condition 1))) |
Generate code for a condition which refers to neighbours within a specified distance.
- NOTE THAT there's clearly masses of commonality between this and
- generate-neighbours-condition , and that some refactoring is almost certainly
- desirable. It may be that it's better to simplify a NEIGHBOURS-CONDITION
- into a WITHIN-CONDITION in the simplification stage.
- | (defn generate-within-condition
- ([tree]
- (assert-type tree :WITHIN-CONDITION)
- (case (first (second tree))
- :QUANTIFIER (generate-within-condition tree (first (second (second tree))))
- :QUALIFIER (TODO "qualified within... help!")))
- ([tree quantifier-type]
- (let [quantifier (second tree)
- distance (generate (nth tree 4))
- pc (generate (nth tree 6))]
- (case quantifier-type
- :NUMBER (generate-neighbours-condition '= (read-string (second (second quantifier))) pc distance)
- :SOME (generate-neighbours-condition '> 0 pc distance)
- :MORE (let [value (generate (nth quantifier 3))]
- (generate-neighbours-condition '> value pc distance))
- :LESS (let [value (generate (nth quantifier 3))]
- (generate-neighbours-condition '< value pc distance)))))) |
Generate code for this (fragment of a) parse tree
- | (defn generate
- [tree]
- (if
- (coll? tree)
- (case (first tree)
- :ACTIONS (generate-multiple-actions tree)
- :COMPARATIVE (generate (second tree))
- :COMPARATIVE-QUALIFIER (generate (second tree))
- :CONDITION (generate-condition tree)
- :CONDITIONS (generate-conditions tree)
- :CONJUNCT-CONDITION (generate-conjunct-condition tree)
- :DISJUNCT-CONDITION (generate-disjunct-condition tree)
- :DISJUNCT-EXPRESSION (generate (nth tree 2))
- :DISJUNCT-VALUE (generate-disjunct-value tree)
- :EQUIVALENCE '=
- :EXPRESSION (generate (second tree))
- :LESS '<
- :MORE '>
- :NEGATED-QUALIFIER (case (generate (second tree))
- = 'not=
- > '<
- < '>)
- :NEIGHBOURS-CONDITION (generate-neighbours-condition tree)
- :NUMERIC-EXPRESSION (generate-numeric-expression tree)
- :NUMBER (read-string (second tree))
- :OPERATOR (symbol (second tree))
- :PROBABLE-ACTION (generate-probable-action tree)
- :PROPERTY (list (generate (second tree)) 'cell) ;; dubious - may not be right
- :PROPERTY-CONDITION (generate-property-condition tree)
- :QUALIFIER (generate-qualifier tree)
- :RULE (generate-rule tree)
- :SIMPLE-ACTION (generate-simple-action tree)
- :SYMBOL (keyword (second tree))
- :VALUE (generate (second tree))
- :WITHIN-CONDITION (generate-within-condition tree)
- (map generate tree))
- tree)) |
| |
| |
Display parse errors in a format which makes it easy for the user
- to see where the error occurred.
- | (ns ^{:doc
- :author "Simon Brooke"}
- mw-parser.errors) |
This program is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-USA.
- | |
error thrown when an attempt is made to set a reserved property
- | (def reserved-properties-error
- "The properties 'x' and 'y' of a cell are reserved and should not be set in rule actions") |
error thrown when a rule cannot be parsed. Slots are for
-(1) rule text
-(2) cursor showing where in the rule text the error occurred
-(3) the reason for the error
- | (def bad-parse-error "I did not understand:\n '%s'\n %s\n %s") |
Attempt to explain the reason for the parse error.
- | (defn- explain-parse-error-reason
- [reason]
- (str "Expecting one of (" (apply str (map #(str (:expecting %) " ") reason)) ")")) |
- | (defn- parser-error-to-map
- [parser-error]
- (let [m (reduce (fn [map item](merge map {(first item)(second item)})) {} parser-error)
- reason (map
- #(reduce (fn [map item] (merge {(first item) (second item)} map)) {} %)
- (:reason m))]
- (merge m {:reason reason}))) |
Construct a helpful error message from this parser-error , and throw an exception with that message.
- | (defn throw-parse-exception
- [parser-error]
- (assert (coll? parser-error) "Expected a paser error structure?")
- (let
- [
- ;; the error structure is a list, such that each element is a list of two items, and
- ;; the first element in each sublist is a keyword. Easier to work with it as a map
- error-map (parser-error-to-map parser-error)
- text (:text error-map)
- reason (explain-parse-error-reason (:reason error-map))
- ;; rules have only one line, by definition; we're interested in the column
- column (if (:column error-map)(:column error-map) 0)
- ;; create a cursor to point to that column
- cursor (apply str (reverse (conj (repeat column " ") "^")))
- message (format bad-parse-error text cursor reason)
- ]
- (throw (Exception. message)))) |
| |
| |
A very simple parser which parses production rules.
- | (ns ^{:doc
- :author "Simon Brooke"}
- mw-parser.core
- (:use mw-engine.utils
- [clojure.string :only [split trim triml]])
- (:gen-class)) |
mw-parser: a rule parser for MicroWorld.
-
-This program is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-USA.
-
-Copyright (C) 2014 Simon Brooke
-
-A very simple parser which parses production rules of the following forms:
-
-
-- "if altitude is less than 100 and state is forest then state should be climax and deer should be 3"
-- "if altitude is 100 or fertility is 25 then state should be heath and fertility should be 24.3"
-- "if altitude is 100 or fertility is 25 then state should be heath"
-- "if deer is more than 2 and wolves is 0 and fertility is more than 20 then deer should be deer + 2"
-- "if deer is more than 1 and wolves is more than 1 then deer should be deer - wolves"
-- "if state is grassland and 4 neighbours have state equal to water then state should be village"
-- "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 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"
-- "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 , 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 parser is now obsolete, but is retained in the codebase for now in
-case it is of use to anyone. Prefer the declarative.clj parser.
- | |
- | (declare parse-conditions)
-(declare parse-not-condition)
-(declare parse-simple-condition) |
a regular expression which matches string representation of positive numbers
- | (def re-number #"^[0-9.]*$") |
error thrown when an attempt is made to set a reserved property
- | (def reserved-properties-error
- "The properties 'x' and 'y' of a cell are reserved and should not be set in rule actions") |
error thrown when a rule cannot be parsed
- | (def bad-parse-error "I did not understand '%s'") |
If this token appears to represent an explicit number, return that number;
- otherwise, make a keyword of it and return that.
- | (defn- keyword-or-numeric
- [token]
- (cond
- (re-matches re-number token) (read-string token)
- (keyword? token) token
- true (keyword token))) |
Generally all functions in this file with names beginning 'parse-' take a
-sequence of tokens (and in some cases other optional arguments) and return a
-vector comprising
-
-
-- A code fragment parsed from the front of the sequence of tokens, and
-- the remaining tokens which were not consumed in constructing that fragment.
-
-
-In every case if the function cannot parse the desired construct from the
-front of the sequence of tokens it returns nil.
- | |
Parse a number.
- | (defn parse-numeric-value
- [[value & remainder]]
- (if (and value (re-matches re-number value)) [(read-string value) remainder])) |
Parse a token assumed to be the name of a property of the current cell,
- whose value is assumed to be an integer.
- | (defn parse-property-int
- [[value & remainder]]
- (if value [(list 'get-int 'cell (keyword value)) remainder])) |
Parse a token assumed to be the name of a property of the current cell.
- | (defn parse-property-value
- [[value & remainder]]
- (if value [(list (keyword value) 'cell) remainder])) |
Parse a token assumed to be a simple token value.
- | (defn parse-token-value
- [[value & remainder]]
- (if value [(keyword value) remainder])) |
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.
- | (defn parse-simple-value
- ([tokens expect-int]
- (or
- (parse-numeric-value tokens)
- (cond expect-int
- (parse-property-int tokens)
- true (parse-token-value tokens))))
- ([tokens]
- (parse-simple-value tokens false))) |
Parse a single value from this single token and return just the generated
- code, not a pair.
- | (defn gen-token-value
- [token expect-int]
- (first (parse-simple-value (list token) expect-int))) |
Parse a list of values from among these tokens . If expect-int is true, return
- integers or things which will evaluate to integers.
- | (defn parse-disjunct-value
- [[OR token & tokens] expect-int]
- (cond (member? OR '("or" "in"))
- (let [value (first (parse-simple-value (list token) expect-int))
- seek-others (= (first tokens) "or")]
- (cond seek-others
- (let [[others remainder] (parse-disjunct-value tokens expect-int)]
- [(cons value others) remainder])
- true
- [(list value) tokens])))) |
Parse a value from among these tokens . If expect-int is true, return
- an integer or something which will evaluate to an integer.
- | (defn parse-value
- ([tokens expect-int]
- (or
- (parse-disjunct-value tokens expect-int)
- (parse-simple-value tokens expect-int)))
- ([tokens]
- (parse-value tokens false))) |
Parses a condition of the form '[property] in [value] or [value]...'
- | (defn parse-member-condition
- [[property IS IN & rest]]
- (if (and (member? IS '("is" "are")) (= IN "in"))
- (let [[l remainder] (parse-disjunct-value (cons "in" rest) false)]
- [(list 'member? (list (keyword property) 'cell) (list 'quote l)) remainder]))) |
Parse '[property] less than [value]'.
- | (defn- parse-less-condition
- [[property IS LESS THAN & rest]]
- (cond (and (member? IS '("is" "are")) (member? LESS '("less" "fewer")) (= THAN "than"))
- (let [[value remainder] (parse-value rest true)]
- [(list '< (list 'get-int 'cell (keyword property)) value) remainder]))) |
Parse '[property] more than [value]'.
- | (defn- parse-more-condition
- [[property IS MORE THAN & rest]]
- (cond (and (member? IS '("is" "are")) (member? MORE '("more" "greater")) (= THAN "than"))
- (let [[value remainder] (parse-value rest true)]
- [(list '> (list 'get-int 'cell (keyword property)) value) remainder]))) |
- | (defn- parse-between-condition
- [[p IS BETWEEN v1 AND v2 & rest]]
- (cond (and (member? IS '("is" "are")) (= BETWEEN "between") (= AND "and") (not (nil? v2)))
- (let [property (first (parse-simple-value (list p) true))
- value1 (first (parse-simple-value (list v1) true))
- value2 (first (parse-simple-value (list v2) true))]
- [(list 'or
- (list '< value1 property value2)
- (list '> value1 property value2)) rest]))) |
Parse clauses of the form 'x is y', 'x is in y or z...',
- 'x is between y and z', 'x is more than y' or 'x is less than y'.
- It is necessary to disambiguate whether value is a numeric or keyword.
- | (defn- parse-is-condition
- [[property IS value & rest]]
- (cond
- (member? IS '("is" "are"))
- (let [tokens (cons property (cons value rest))]
- (cond
- (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])))) |
Parse the negation of a simple condition.
- | (defn- parse-not-condition
- [[property IS NOT & rest]]
- (cond (and (member? IS '("is" "are")) (= NOT "not"))
- (let [partial (parse-simple-condition (cons property (cons "is" rest)))]
- (cond partial
- (let [[condition remainder] partial]
- [(list 'not condition) remainder]))))) |
- | (defn- gen-neighbours-condition
- ([comp1 quantity property value remainder comp2 distance]
- [(list comp1
- (list 'count
- (list 'get-neighbours-with-property-value 'world
- '(cell :x) '(cell :y) distance
- (keyword property) (keyword-or-numeric value) comp2))
- quantity)
- remainder])
- ([comp1 quantity property value remainder comp2]
- (gen-neighbours-condition comp1 quantity property value remainder comp2 1))) |
Parse conditions of the form '...more than 6 neighbours are [condition]'
- | (defn parse-comparator-neighbours-condition
- [[MORE THAN n NEIGHBOURS WITHIN distance have-or-are & rest]]
- (let [quantity (first (parse-numeric-value (list n)))
- comparator (cond (= MORE "more") '>
- (member? MORE '("fewer" "less")) '<)]
- (cond
- (not= WITHIN "within")
- (parse-comparator-neighbours-condition
- (flatten
- ;; two tokens were mis-parsed as 'within distance' that weren't
- ;; actually 'within' and a distance. Splice in 'within 1' and try
- ;; again.
- (list MORE THAN n NEIGHBOURS "within" "1" WITHIN distance have-or-are rest)))
- (and quantity
- comparator
- (= THAN "than")
- (= NEIGHBOURS "neighbours"))
- (cond
- (= have-or-are "are")
- (let [[value & remainder] rest
- dist (gen-token-value distance true)]
- (gen-neighbours-condition comparator quantity :state value remainder = dist))
- (= have-or-are "have")
- (let [[property comp1 comp2 value & remainder] rest
- dist (gen-token-value distance true)]
- (cond (and (= comp1 "equal") (= comp2 "to"))
- (gen-neighbours-condition comparator quantity property
- value remainder = dist)
- (and (= comp1 "more") (= comp2 "than"))
- (gen-neighbours-condition comparator quantity property
- value remainder > dist)
- (and (= comp1 "less") (= comp2 "than"))
- (gen-neighbours-condition comparator quantity property
- value remainder < dist))))))) |
- | (defn parse-some-neighbours-condition
- [[SOME NEIGHBOURS & rest]]
- (cond
- (and (= SOME "some") (= NEIGHBOURS "neighbours"))
- (parse-comparator-neighbours-condition (concat '("more" "than" "0" "neighbours") rest)))) |
Parse conditions of the form '...6 neighbours are [condition]'
- | (defn parse-simple-neighbours-condition
- [[n NEIGHBOURS WITHIN distance have-or-are & rest]]
- (let [quantity (first (parse-numeric-value (list n)))]
- (cond
- (and quantity (= NEIGHBOURS "neighbours"))
- (cond
- (not= WITHIN "within")
- (parse-simple-neighbours-condition
- (flatten
- ;; two tokens were mis-parsed as 'within distance' that weren't
- ;; actually 'within' and a distance. Splice in 'within 1' and try
- ;; again.
- (list n NEIGHBOURS "within" "1" WITHIN distance have-or-are rest)))
- (= have-or-are "are")
- (let [[value & remainder] rest
- dist (gen-token-value distance true)]
- (gen-neighbours-condition '= quantity :state value remainder = dist))
- (= have-or-are "have")
- (let [[property comp1 comp2 value & remainder] rest
- dist (gen-token-value distance true)]
- (cond (and (= comp1 "equal") (= comp2 "to"))
- (gen-neighbours-condition '= quantity property value remainder =
- dist)
- (and (= comp1 "more") (= comp2 "than"))
- (gen-neighbours-condition '= quantity property value remainder >
- dist)
- (and (= comp1 "less") (= comp2 "than"))
- (gen-neighbours-condition '= quantity property value remainder <
- dist))))))) |
Parse conditions referring to neighbours
- | (defn parse-neighbours-condition
- [tokens]
- (or
- (parse-simple-neighbours-condition tokens)
- (parse-comparator-neighbours-condition tokens)
- (parse-some-neighbours-condition tokens))) |
Parse conditions of the form '[property] [comparison] [value]'.
- | (defn parse-simple-condition
- [tokens]
- (or
- (parse-neighbours-condition tokens)
- (parse-member-condition tokens)
- (parse-not-condition tokens)
- (parse-less-condition tokens)
- (parse-more-condition tokens)
- (parse-between-condition tokens)
- (parse-is-condition tokens))) |
Parse '... or [condition]' from tokens , where left is the already parsed first disjunct.
- | (defn- parse-disjunction-condition
- [left tokens]
- (let [partial (parse-conditions tokens)]
- (if partial
- (let [[right remainder] partial]
- [(list 'or left right) remainder])))) |
Parse '... and [condition]' from tokens , where left is the already parsed first conjunct.
- | (defn- parse-conjunction-condition
- [left tokens]
- (let [partial (parse-conditions tokens)]
- (if partial
- (let [[right remainder] partial]
- [(list 'and left right) remainder])))) |
Parse conditions from tokens , where conditions may be linked by either 'and' or 'or'.
- | (defn- parse-conditions
- [tokens]
- (let [partial (parse-simple-condition tokens)]
- (if partial
- (let [[left [next & remainder]] partial]
- (cond
- (= next "and") (parse-conjunction-condition left remainder)
- (= next "or") (parse-disjunction-condition left remainder)
- true partial))))) |
Parse the left hand side ('if...') of a production rule.
- | (defn- parse-left-hand-side
- [[IF & tokens]]
- (if
- (= IF "if")
- (parse-conditions tokens))) |
Parse actions of the form '[property] should be [property] [arithmetic-operator] [value]',
- e.g. 'fertility should be fertility + 1', or 'deer should be deer - wolves'.
- | (defn- parse-arithmetic-action
- [previous [prop1 SHOULD BE prop2 operator value & rest]]
- (cond
- (member? prop1 '("x" "y"))
- (throw
- (Exception. reserved-properties-error))
- (and (= SHOULD "should")
- (= BE "be")
- (member? operator '("+" "-" "*" "/")))
- [(list 'merge (or previous 'cell)
- {(keyword prop1) (list 'int
- (list (symbol operator) (list 'get-int 'cell (keyword prop2))
- (cond
- (re-matches re-number value) (read-string value)
- true (list 'get-int 'cell (keyword value)))))}) rest])) |
Parse actions of the form '[property] should be [value].'
- | (defn- parse-set-action
- [previous [property SHOULD BE value & rest]]
- (cond
- (member? property '("x" "y"))
- (throw
- (Exception. reserved-properties-error))
- (and (= SHOULD "should") (= BE "be"))
- [(list 'merge (or previous 'cell)
- {(keyword property) (cond (re-matches re-number value) (read-string value) true (keyword value))}) rest])) |
- | (defn- parse-simple-action [previous tokens]
- (or (parse-arithmetic-action previous tokens)
- (parse-set-action previous tokens))) |
Parse actions from tokens.
- | (defn- parse-actions
- [previous tokens]
- (let [[left remainder] (parse-simple-action previous tokens)]
- (cond left
- (cond (= (first remainder) "and")
- (parse-actions left (rest remainder))
- true (list left))))) |
Parse a probability of an action from this collection of tokens
- | (defn- parse-probability
- [previous [n CHANCE IN m & tokens]]
- (cond
- (and (= CHANCE "chance")(= IN "in"))
- (let [[action remainder] (parse-actions previous tokens)]
- (cond action
- [(list 'cond
- (list '<
- (list 'rand
- (first (parse-simple-value (list m) true)))
- (first (parse-simple-value (list n) true)))
- action) remainder])))) |
Parse the right hand side ('then...') of a production rule.
- | (defn- parse-right-hand-side
- [[THEN & tokens]]
- (if (= THEN "then")
- (or
- (parse-probability nil tokens)
- (parse-actions nil 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.
- | (defn parse-rule
- [line]
- (cond
- (string? line)
- (let [rule (parse-rule (split (triml line) #"\s+"))]
- (cond rule rule
- true (throw (Exception. (format bad-parse-error line)))))
- true
- (let [[left remainder] (parse-left-hand-side line)
- [right junk] (parse-right-hand-side remainder)]
- (cond
- ;; there should be a valide left hand side and a valid right hand side
- ;; there shouldn't be anything left over (junk should be empty)
- (and left right (empty? junk))
- (list 'fn ['cell 'world] (list 'if left right)))))) |
Parse this rule-text , a string conforming to the grammar of MicroWorld rules,
- into Clojure source, and then compile it into an anonymous
- function object, getting round the problem of binding mw-engine.utils in
- the compiling environment. If return-tuple? is present and true, return
- a list comprising the anonymous function compiled, and the function from
- which it was compiled.
-
- Throws an exception if parsing fails.
- | (defn compile-rule
- ([rule-text return-tuple?]
- (do
- (use 'mw-engine.utils)
- (let [afn (eval (parse-rule rule-text))]
- (cond
- (and afn return-tuple?)(list afn (trim rule-text))
- true afn))))
- ([rule-text]
- (compile-rule rule-text false))) |
| |
| |
parse multiple rules from a stream, possibly a file.
- | (ns ^{:doc
- :author "Simon Brooke"}
- mw-parser.bulk
- (:use mw-parser.core
- mw-engine.utils
- clojure.java.io
- [clojure.string :only [split trim]])
- (:import (java.io BufferedReader StringReader))) |
mw-parser: a rule parser for MicroWorld.
-
-This program is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-USA.
-
-Copyright (C) 2014 Simon Brooke
- | |
Is this line a comment?
- | (defn comment?
- [line]
- (or (empty? (trim line)) (member? (first line) '(nil \# \;)))) |
Parse rules from successive lines in this string , assumed to have multiple
- lines delimited by the new-line character. Return a list of S-expressions.
- | (defn parse-string
- [string]
- ;; TODO: tried to do this using with-open, but couldn't make it work.
- (map #(parse-rule (trim %)) (remove comment? (split string #"\n")))) |
Parse rules from successive lines in the file loaded from this filename .
- Return a list of S-expressions.
- | (defn parse-file
- [filename]
- (parse-string (slurp filename))) |
Compile each non-comment line of this string into an executable anonymous
- function, and return the sequence of such functions.
- | (defn compile-string
- [string]
- (map #(compile-rule % true) (remove comment? (split string #"\n")))) |
Compile each non-comment line of the file indicated by this filename into
- an executable anonymous function, and return the sequence of such functions.
- | (defn compile-file
- [filename]
- (compile-string (slurp filename))) |
| |
| |
Simplify a parse tree.
- | (ns ^{:doc
- :author "Simon Brooke"}
- mw-parser.simplify
- (:require [mw-engine.utils :refer [member?]])) |
mw-parser: a rule parser for MicroWorld.
-
-This program is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-USA.
-
-Copyright (C) 2014 Simon Brooke
- | |
- | (declare simplify) |
Given that this tree fragment represents a qualifier, what
- qualifier is that?
- | (defn simplify-qualifier
- [tree]
- (cond
- (empty? tree) nil
- (and (coll? tree)
- (member? (first tree) '(:EQUIVALENCE :COMPARATIVE))) tree
- (coll? (first tree)) (or (simplify-qualifier (first tree))
- (simplify-qualifier (rest tree)))
- (coll? tree) (simplify-qualifier (rest tree))
- true tree)) |
There are a number of possible simplifications such that if the tree has
- only two elements, the second is semantically sufficient.
- | (defn simplify-second-of-two
- [tree]
- (if (= (count tree) 2) (simplify (nth tree 1)) tree)) |
If this quantifier is a number, 'simplifiy' it into a comparative whose operator is '='
- and whose quantity is that number. This is actually more complicated but makes generation easier.
- | (defn simplify-quantifier
- [tree]
- (if (number? (second tree)) [:COMPARATIVE '= (second tree)] (simplify (second tree)))) |
Simplify/canonicalise this tree . Opportunistically replace complex fragments with
- semantically identical simpler fragments
- | (defn simplify
- [tree]
- (if
- (coll? tree)
- (case (first tree)
- :ACTION (simplify-second-of-two tree)
- :ACTIONS (cons (first tree) (simplify (rest tree)))
- :CHANCE-IN nil
- :COMPARATIVE (simplify-second-of-two tree)
- :CONDITION (simplify-second-of-two tree)
- :CONDITIONS (simplify-second-of-two tree)
- :EXPRESSION (simplify-second-of-two tree)
- :PROPERTY (simplify-second-of-two tree)
- :PROPERTY-CONDITION-OR-EXPRESSION (simplify-second-of-two tree)
- :SPACE nil
- :THEN nil
- :AND nil
- :VALUE (simplify-second-of-two tree)
- (remove nil? (map simplify tree)))
- tree)) |
| |
| |
Utilities used in more than one namespace within the parser.
- | (ns ^{:doc
- :author "Simon Brooke"}
- mw-parser.utils) |
mw-parser: a rule parser for MicroWorld.
-
-This program is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-USA.
-
-Copyright (C) 2014 Simon Brooke
- | |
Return true if the argument appears to be a parsed rule tree, else false.
- | (defn rule?
- [maybe-rule]
- (and (coll? maybe-rule) (= (first maybe-rule) :RULE))) |
Marker to indicate I'm not yet finished!
- | (defn TODO
- [message]
- message) |
Return true if tree-fragment appears to be a tree fragment of the expected type .
- | (defn suitable-fragment?
- [tree-fragment type]
- (and (coll? tree-fragment)
- (= (first tree-fragment) type))) |
If tree-fragment is not a tree fragment of the expected type , throw an exception.
- | (defn assert-type
- [tree-fragment type]
- (assert (suitable-fragment? tree-fragment type)
- (throw (Exception. (format "Expected a %s fragment" type))))) |
Return the first element of this tree which has this tag in a depth-first, left-to-right search
- | (defn search-tree
- [tree tag]
- (cond
- (= (first tree) tag) tree
- :else (first
- (remove nil?
- (map
- #(search-tree % tag)
- (rest tree)))))) |
| |
\ No newline at end of file
diff --git a/project.clj b/project.clj
index 77f0874..4552bd6 100644
--- a/project.clj
+++ b/project.clj
@@ -1,18 +1,23 @@
(defproject mw-parser "0.2.0-SNAPSHOT"
- :description "Parser for production rules for MicroWorld engine"
- :url "http://www.journeyman.cc/microworld"
- :manifest {
- "build-signature-version" "unset"
- "build-signature-user" "unset"
- "build-signature-email" "unset"
- "build-signature-timestamp" "unset"
- "Implementation-Version" "unset"
- }
- :license {:name "GNU General Public License v2"
- :url "http://www.gnu.org/licenses/gpl-2.0.html"}
- :plugins [[lein-marginalia "0.7.1"]]
+ :cloverage {:output "docs/cloverage"}
+ :codox {:metadata {:doc "**TODO**: write docs"
+ :doc/format :markdown}
+ :output-path "docs/codox"
+ :source-uri "https://github.com/simon-brooke/mw-parser/blob/master/{filepath}#L{line}"}
:dependencies [[org.clojure/clojure "1.11.1"]
[org.clojure/tools.trace "0.7.11"]
[instaparse "1.4.12"]
[mw-engine "0.2.0-SNAPSHOT"]
- [trptr/java-wrapper "0.2.3"]])
+ [trptr/java-wrapper "0.2.3"]]
+ :description "Parser for production rules for MicroWorld engine"
+ :license {:name "GNU General Public License v2"
+ :url "http://www.gnu.org/licenses/gpl-2.0.html"}
+ :manifest {"build-signature-version" "unset"
+ "build-signature-user" "unset"
+ "build-signature-email" "unset"
+ "build-signature-timestamp" "unset"
+ "Implementation-Version" "unset"}
+ :plugins [[lein-marginalia "0.7.1"]
+ [lein-cloverage "1.2.2"]
+ [lein-codox "0.10.8"]]
+ :url "http://www.journeyman.cc/microworld")
diff --git a/src/mw_parser/declarative.clj b/src/mw_parser/declarative.clj
index a0f6b39..fb79e89 100644
--- a/src/mw_parser/declarative.clj
+++ b/src/mw_parser/declarative.clj
@@ -1,7 +1,7 @@
(ns ^{:doc "A very simple parser which parses production rules."
:author "Simon Brooke"}
mw-parser.declarative
- (:require [instaparse.core :as insta]
+ (:require [instaparse.core :refer [parser]]
[clojure.string :refer [join trim]]
[mw-parser.errors :refer [throw-parse-exception]]
[mw-parser.generate :refer [generate]]
@@ -119,12 +119,18 @@
non-latin alphabets, anyway."
([]
(keywords-for-locale (get-default)))
- ([^Locale locale]
+ ([^Locale _locale]
keywords-en))
+(defmacro build-parser
+ "Compose this grammar fragment `g` with the common grammar fragments to
+ make a complete grammar, and return a parser for that complete grammar."
+ [g]
+ `(parser (join "\n" [~g common-grammar (keywords-for-locale)])))
+
(def parse-rule
"Parse the argument, assumed to be a string in the correct syntax, and return a parse tree."
- (insta/parser (join "\n" [rule-grammar common-grammar (keywords-for-locale)])))
+ (build-parser rule-grammar))
(defn compile-rule
"Parse this `rule-text`, a string conforming to the grammar of MicroWorld rules,
diff --git a/src/mw_parser/flow.clj b/src/mw_parser/flow.clj
index adaacf4..293a5cb 100644
--- a/src/mw_parser/flow.clj
+++ b/src/mw_parser/flow.clj
@@ -2,25 +2,66 @@
:author "Simon Brooke"}
mw-parser.flow
(:require [clojure.string :refer [join]]
- [instaparse.core :as insta]
- [mw-parser.declarative :refer [common-grammar keywords-for-locale]]))
+ [mw-parser.declarative :refer [build-parser]]
+ [mw-parser.simplify :refer [simplify-second-of-two]]))
(def flow-grammar
- "Grammar for flow rules"
+ "Grammar for flow rules.
+
+ My initial conception of this would be that production rules
+ (if-then rules) and flow rules (flow-from-to rules) would be
+ entirely separate, presented to the parser as separate text
+ files, and parsed and compiled by different chains of functions.
+
+ This appears not to be necessary. Flow rules are easy to parse
+ with the same parser as production rules -- a lot of the grammar
+ is intentionally common -- and the rules are easily discriminated
+ at the compilation ('generate') stage.
+
+ The basic rule I want to be able to compile at this stage is the 'mutual
+ aid' rule:
+
+ `flow 1 food from house having food > 1 to house with least food within 2`
+ "
(join "\n" ["FLOW-RULE := FLOW SPACE QUANTITY SPACE PROPERTY SPACE FROM SPACE SOURCE SPACE TO-HOW SPACE DESTINATION;"
"PERCENTAGE := NUMBER #'%';"
- "QUANTITY := PERCENTAGE | NUMBER | SOME;"
+ "QUANTITY := PERCENTAGE | NUMBER | EXPRESSION | SOME;"
"SOURCE := STATE | STATE SPACE WITH SPACE CONDITIONS;"
- "DESTINATION := STATE | STATE SPACE WITH SPACE FLOW-CONDITIONS;"
+ "DESTINATION := STATE | STATE SPACE WITH SPACE FLOW-CONDITIONS | STATE SPACE WITHIN SPACE VALUE SPACE WITH SPACE FLOW-CONDITIONS;"
"DETERMINER := MOST | LEAST;"
- "DETERMINER-CONDITION := DETERMINER SPACE PROPERTY | DETERMINER SPACE PROPERTY SPACE WITHIN SPACE NUMBER;"
+ "DETERMINER-CONDITION := DETERMINER SPACE PROPERTY | DETERMINER SPACE PROPERTY;"
"FLOW-CONDITIONS := DETERMINER-CONDITION | CONDITIONS"
"STATE := SYMBOL;"
"TO-HOW := TO | TO-EACH | TO-FIRST;"
"TO-EACH := TO SPACE EACH | TO SPACE ALL;"
- "TO-FIRST := TO SPACE FIRST"
- ]))
+ "TO-FIRST := TO SPACE FIRST"]))
(def parse-flow
"Parse the argument, assumed to be a string in the correct syntax, and return a parse tree."
- (insta/parser (join "\n" [flow-grammar common-grammar (keywords-for-locale)])))
+ (build-parser flow-grammar))
+
+(defn simplify-flow
+ [tree]
+ (if (coll? tree)
+ (case (first tree)
+ :CONDITION (simplify-second-of-two tree)
+ :CONDITIONS (simplify-second-of-two tree)
+ :DETERMINER (simplify-second-of-two tree)
+;; :DETERMINER-CONDITION (simplify-determiner-condition tree)
+ :EXPRESSION (simplify-second-of-two tree)
+ :FLOW nil
+;; :FLOW-CONDITIONS (simplify-second-of-two tree)
+ :PROPERTY (simplify-second-of-two tree)
+ :PROPERTY-CONDITION-OR-EXPRESSION (simplify-second-of-two tree)
+ :SPACE nil
+ :QUANTITY (simplify-second-of-two tree)
+ :STATE (list :PROPERTY-CONDITION
+ (list :SYMBOL "state")
+ '(:QUALIFIER
+ (:EQUIVALENCE
+ (:IS "is")))
+ (list :EXPRESSION
+ (list :VALUE (second tree))))
+ (remove nil? (map simplify-flow tree)))
+ tree))
+
diff --git a/src/mw_parser/generate.clj b/src/mw_parser/generate.clj
index 6a1d318..32d11f5 100644
--- a/src/mw_parser/generate.clj
+++ b/src/mw_parser/generate.clj
@@ -1,9 +1,8 @@
(ns ^{:doc "Generate Clojure source from simplified parse trees."
:author "Simon Brooke"}
- mw-parser.generate
- (:require [mw-engine.utils :refer []]
- [mw-parser.utils :refer [assert-type TODO]]
- [mw-parser.errors :as pe]))
+ mw-parser.generate
+ (:require [mw-parser.utils :refer [assert-type TODO]]
+ [mw-parser.errors :as pe]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
@@ -73,14 +72,14 @@
this `property` where the `expression` is a numeric range, generate and return
the appropriate clojure fragment."
[tree property expression]
- (assert-type tree :PROPERTY-CONDITION)
- (assert-type (nth tree 3) :RANGE-EXPRESSION)
- (let [l1 (generate (nth expression 2))
- l2 (generate (nth expression 4))
- pv (list property 'cell)]
- (list 'let ['lower (list 'min l1 l2)
- 'upper (list 'max l1 l2)]
- (list 'and (list '>= pv 'lower)(list '<= pv 'upper)))))
+ (assert-type tree :PROPERTY-CONDITION)
+ (assert-type (nth tree 3) :RANGE-EXPRESSION)
+ (let [l1 (generate (nth expression 2))
+ l2 (generate (nth expression 4))
+ pv (list property 'cell)]
+ (list 'let ['lower (list 'min l1 l2)
+ 'upper (list 'max l1 l2)]
+ (list 'and (list '>= pv 'lower) (list '<= pv 'upper)))))
(defn generate-disjunct-property-condition
@@ -97,7 +96,7 @@
(let [e (list 'some (list 'fn ['i] '(= i value)) (list 'quote expression))]
(list 'let ['value (list property 'cell)]
(if (= qualifier '=) e
- (list 'not e))))))
+ (list 'not e))))))
(defn generate-property-condition
@@ -106,15 +105,15 @@
([tree]
(assert-type tree :PROPERTY-CONDITION)
(if
- (and (= (count tree) 2) (= (first (second tree)) :SYMBOL))
+ (and (= (count tree) 2) (= (first (second tree)) :SYMBOL))
;; it's a shorthand for 'state equal to symbol'. This should probably have
;; been handled in simplify...
(generate-property-condition
- (list
- :PROPERTY-CONDITION
- '(:SYMBOL "state")
- '(:QUALIFIER (:EQUIVALENCE (:EQUAL "equal to")))
- (second tree)))
+ (list
+ :PROPERTY-CONDITION
+ '(:SYMBOL "state")
+ '(:QUALIFIER (:EQUIVALENCE (:EQUAL "equal to")))
+ (second tree)))
;; otherwise...
(generate-property-condition tree (first (nth tree 3)))))
([tree expression-type]
@@ -131,18 +130,16 @@
:RANGE-EXPRESSION (generate-ranged-property-condition tree property expression)
(list qualifier (list property 'cell) expression)))))
-
(defn generate-qualifier
"From this `tree`, assumed to be a syntactically correct qualifier,
generate and return the appropriate clojure fragment."
[tree]
(if
- (= (count tree) 2)
+ (= (count tree) 2)
(generate (second tree))
;; else
(generate (nth tree 2))))
-
(defn generate-simple-action
"From this `tree`, assumed to be a syntactically correct simple action,
generate and return the appropriate clojure fragment."
@@ -158,10 +155,9 @@
(list 'merge
(if (empty? others) 'cell
;; else
- (generate others))
+ (generate others))
{property expression})))))
-
(defn generate-probable-action
"From this `tree`, assumed to be a syntactically correct probable action,
generate and return the appropriate clojure fragment."
@@ -170,15 +166,14 @@
(generate-probable-action tree []))
([tree others]
(assert-type tree :PROBABLE-ACTION)
- (let
+ (let
[chances (generate (nth tree 1))
total (generate (nth tree 2))
action (generate-action (nth tree 3) others)]
;; TODO: could almost certainly be done better with macro syntax
- (list 'if
- (list '< (list 'rand total) chances)
- action))))
-
+ (list 'if
+ (list '< (list 'rand total) chances)
+ action))))
(defn generate-action
"From this `tree`, assumed to be a syntactically correct action,
@@ -190,7 +185,6 @@
:PROBABLE-ACTION (generate-probable-action tree others)
(throw (Exception. (str "Not a known action type: " (first tree))))))
-
(defn generate-multiple-actions
"From this `tree`, assumed to be one or more syntactically correct actions,
generate and return the appropriate clojure fragment."
@@ -207,7 +201,6 @@
(cons (generate (second tree)) (generate (nth tree 3)))
(list (generate (second tree)))))
-
(defn generate-numeric-expression
"From this `tree`, assumed to be a syntactically correct numeric expression,
generate and return the appropriate clojure fragment."
@@ -221,7 +214,6 @@
:SYMBOL (list (keyword (second (second tree))) 'cell)
(generate (second tree)))))
-
(defn generate-neighbours-condition
"Generate code for a condition which refers to neighbours."
([tree]
@@ -239,8 +231,7 @@
:MORE (let [value (generate (nth quantifier 3))]
(generate-neighbours-condition '> value pc 1))
:LESS (let [value (generate (nth quantifier 3))]
- (generate-neighbours-condition '< value pc 1))
- )))
+ (generate-neighbours-condition '< value pc 1)))))
([comp1 quantity property-condition distance]
(list comp1
(list 'count
@@ -258,10 +249,10 @@
desirable. It may be that it's better to simplify a `NEIGHBOURS-CONDITION`
into a `WITHIN-CONDITION` in the simplification stage."
([tree]
- (assert-type tree :WITHIN-CONDITION)
- (case (first (second tree))
- :QUANTIFIER (generate-within-condition tree (first (second (second tree))))
- :QUALIFIER (TODO "qualified within... help!")))
+ (assert-type tree :WITHIN-CONDITION)
+ (case (first (second tree))
+ :QUANTIFIER (generate-within-condition tree (first (second (second tree))))
+ :QUALIFIER (TODO "qualified within... help!")))
([tree quantifier-type]
(let [quantifier (second tree)
distance (generate (nth tree 4))
@@ -272,15 +263,17 @@
:MORE (let [value (generate (nth quantifier 3))]
(generate-neighbours-condition '> value pc distance))
:LESS (let [value (generate (nth quantifier 3))]
- (generate-neighbours-condition '< value pc distance))
- ))))
+ (generate-neighbours-condition '< value pc distance))))))
+(defn generate-flow
+ [tree]
+ (assert-type tree :WITHIN-CONDITION))
(defn generate
"Generate code for this (fragment of a) parse tree"
[tree]
(if
- (coll? tree)
+ (coll? tree)
(case (first tree)
:ACTIONS (generate-multiple-actions tree)
:COMPARATIVE (generate (second tree))
@@ -293,12 +286,13 @@
:DISJUNCT-VALUE (generate-disjunct-value tree)
:EQUIVALENCE '=
:EXPRESSION (generate (second tree))
+ :FLOW-RULE (generate-flow tree)
:LESS '<
:MORE '>
:NEGATED-QUALIFIER (case (generate (second tree))
- = 'not=
- > '<
- < '>)
+ = 'not=
+ > '<
+ < '>)
:NEIGHBOURS-CONDITION (generate-neighbours-condition tree)
:NUMERIC-EXPRESSION (generate-numeric-expression tree)
:NUMBER (read-string (second tree))
diff --git a/src/mw_parser/simplify.clj b/src/mw_parser/simplify.clj
index 44d81bb..7c5b45a 100644
--- a/src/mw_parser/simplify.clj
+++ b/src/mw_parser/simplify.clj
@@ -1,8 +1,6 @@
(ns ^{:doc "Simplify a parse tree."
:author "Simon Brooke"}
- mw-parser.simplify
- (:require [clojure.pprint :refer [pprint]]
- [mw-engine.utils :refer [member?]]))
+ mw-parser.simplify)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;
@@ -59,7 +57,7 @@
semantically identical simpler fragments"
[tree]
(if
- (coll? tree)
+ (coll? tree)
(case (first tree)
:ACTION (simplify-second-of-two tree)
:ACTIONS (cons (first tree) (simplify-rule (rest tree)))
@@ -78,24 +76,9 @@
tree))
(defn simplify-determiner-condition
- [tree])
-
-(defn simplify-flow
[tree]
- (if (coll? tree)
- (case (first tree)
- :FLOW nil
- :DETERMINER (simplify-second-of-two tree)
- :DETERMINER-CONDITION (simplify-determiner-condition tree)
- :SPACE nil
- :QUANTITY (simplify-second-of-two tree)
- :STATE [:PROPERTY-CONDITION
- [:SYMBOL "state"]
- [:QUALIFIER
- [:EQUIVALENCE
- [:IS "is"]]]
- [:EXPRESSION
- [:VALUE
- (second tree)]]]
- (remove nil? (map simplify-flow tree)))
- tree))
\ No newline at end of file
+ (apply vector
+ (cons :DETERMINER-CONDITION
+ (cons
+ (simplify-second-of-two (second tree))
+ (rest (rest tree))))))
diff --git a/src/mw_parser/utils.clj b/src/mw_parser/utils.clj
index e8bdca8..c846478 100644
--- a/src/mw_parser/utils.clj
+++ b/src/mw_parser/utils.clj
@@ -26,11 +26,17 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defn suitable-fragment?
+ "Return `true` if `tree-fragment` appears to be a tree fragment of the expected `type`."
+ [tree-fragment type]
+ (and (coll? tree-fragment)
+ (keyword? type)
+ (= (first tree-fragment) type)))
+
(defn rule?
"Return true if the argument appears to be a parsed rule tree, else false."
[maybe-rule]
- (and (coll? maybe-rule) (= (first maybe-rule) :RULE)))
-
+ (suitable-fragment? maybe-rule :RULE))
(defn TODO
"Marker to indicate I'm not yet finished!"
@@ -38,12 +44,6 @@
message)
-(defn suitable-fragment?
- "Return `true` if `tree-fragment` appears to be a tree fragment of the expected `type`."
- [tree-fragment type]
- (and (coll? tree-fragment)
- (= (first tree-fragment) type)))
-
(defn assert-type
"If `tree-fragment` is not a tree fragment of the expected `type`, throw an exception."
@@ -55,10 +55,10 @@
(defn search-tree
"Return the first element of this tree which has this tag in a depth-first, left-to-right search"
[tree tag]
- (cond
+ (cond
(= (first tree) tag) tree
:else (first
(remove nil?
(map
#(search-tree % tag)
- (rest tree))))))
+ (filter coll? (rest tree)))))))
diff --git a/test/mw_parser/declarative_test.clj b/test/mw_parser/declarative_test.clj
index bc7485f..c486fc2 100644
--- a/test/mw_parser/declarative_test.clj
+++ b/test/mw_parser/declarative_test.clj
@@ -1,11 +1,10 @@
(ns mw-parser.declarative-test
- (:use clojure.pprint
- mw-engine.core
- mw-engine.world
- mw-engine.utils
- mw-parser.utils)
- (:require [clojure.test :refer :all]
- [mw-parser.declarative :refer :all]))
+ (:require [clojure.test :refer [deftest is testing]]
+ [mw-engine.core :refer [transform-world]]
+ [mw-engine.utils :refer [get-cell]]
+ [mw-engine.world :refer [make-world]]
+ [mw-parser.declarative :refer [compile-rule parse-rule]]
+ [mw-parser.utils :refer [rule?]]))
(deftest rules-tests
(testing "Rule parser - does not test whether generated functions actually work, just that something is generated!"
@@ -18,8 +17,7 @@
(is (rule? (parse-rule "if deer is more than 1 and wolves is more than 1 then deer should be deer - wolves")))
(is (rule? (parse-rule "if state is forest and fertility is between 55 and 75 then state should be climax")))
(is (rule? (parse-rule "if fertility is between 55 and 75 then state should be climax")))
- (is (rule? (parse-rule "if altitude is less than 100 and state is forest then state should be climax and deer should be 3")))
- ))
+ (is (rule? (parse-rule "if altitude is less than 100 and state is forest then state should be climax and deer should be 3")))))
(deftest neighbours-rules-tests
(testing "Rules which relate to neighbours - hard!"
diff --git a/test/mw_parser/flow_test.clj b/test/mw_parser/flow_test.clj
new file mode 100644
index 0000000..2167062
--- /dev/null
+++ b/test/mw_parser/flow_test.clj
@@ -0,0 +1,66 @@
+(ns mw-parser.flow-test
+ (:require ;; [clojure.pprint :as pprint]
+ [clojure.test :refer [deftest is testing]] ;; [mw-engine.core :refer [transform-world]]
+ [mw-parser.flow :refer [parse-flow simplify-flow]]))
+
+(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
+ (: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
+ (: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")))))
+ actual (simplify-flow (parse-flow 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
+ (: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
+ (:PROPERTY-CONDITION
+ (:SYMBOL "state")
+ (:QUALIFIER (:EQUIVALENCE (:IS "is")))
+ (:EXPRESSION (:VALUE [:SYMBOL "house"])))
+ (:WITHIN "within")
+ (:VALUE (:NUMBER "2"))
+ (:WITH "with")
+ (:FLOW-CONDITIONS
+ (:PROPERTY-CONDITION
+ (:SYMBOL "food")
+ (:QUALIFIER (:COMPARATIVE-QUALIFIER (:LESS "less") (:THAN "than")))
+ (:NUMBER "4")))))
+ actual (simplify-flow (parse-flow rule))]
+ (is (= actual expected) rule))))
diff --git a/test/mw_parser/utils_test.clj b/test/mw_parser/utils_test.clj
new file mode 100644
index 0000000..653fc92
--- /dev/null
+++ b/test/mw_parser/utils_test.clj
@@ -0,0 +1,30 @@
+(ns mw-parser.utils-test
+ (:require [clojure.test :refer [deftest is testing]]
+ [mw-parser.utils :refer [assert-type rule? search-tree
+ suitable-fragment? TODO]]))
+
+(deftest fragment-tests
+ (testing "Functions finding and identifying rule fragments"
+ (let [rule '(:RULE
+ (:IF "if")
+ (:PROPERTY-CONDITION
+ (:SYMBOL "state")
+ (:QUALIFIER (:EQUIVALENCE (:IS "is")))
+ (:SYMBOL "forest"))
+ (:ACTIONS
+ (:SIMPLE-ACTION
+ (:SYMBOL "state")
+ (:BECOMES "should be")
+ (:SYMBOL "climax"))))
+ not-rule [:FROBOZ :foo :bar :ban]]
+ (is (rule? rule))
+ (is (not (rule? not-rule)))
+ (is (= nil (assert-type rule :RULE)))
+ (is (thrown-with-msg?
+ Exception #"Expected a :RULE fragment" (assert-type not-rule :RULE)))
+ (is (= '(:EQUIVALENCE (:IS "is")) (search-tree rule :EQUIVALENCE)))
+ (is (= nil (search-tree rule :EQUIVOCATION)))
+ (is (suitable-fragment? '(:EQUIVALENCE (:IS "is")) :EQUIVALENCE))
+ (is (not (suitable-fragment? :EQUIVALENCE :EQUIVALENCE)))
+ (is (not (suitable-fragment? '(:EQUIVALENCE (:IS "is")) :QUALIFIER)))
+ (is (= (TODO "Froboz") "Froboz")))))
\ No newline at end of file