Added protection against null pointer exceptions in numeric properties.

This commit is contained in:
Simon Brooke 2023-07-21 23:52:04 +01:00
parent b4f796aca4
commit a436499d98
4 changed files with 73 additions and 15 deletions

2
.gitignore vendored
View file

@ -9,3 +9,5 @@ pom.xml
.nrepl-port .nrepl-port
doc/scratch.clj doc/scratch.clj
src/mw_parser/scratch.clj

View file

@ -13,15 +13,49 @@ You can see MicroWorld in action [here](http://www.journeyman.cc/microworld/) -
but please don't be mean to my poor little server. If you want to run big maps but please don't be mean to my poor little server. If you want to run big maps
or complex rule-sets, please run it on your own machines. or complex rule-sets, please run it on your own machines.
### Version compatibility
There are substantial changes in how rule functions are evaluated between 0.1.x
versions of MicroWorld libraries and 0.3.x versions. In particular, in 0.3.x
metadata is held on rule functions which is essential to the functioning of the
engine. Consequently, you cannot mix 0.1.x and 0.3.x libraries: it will not work.
In particular the parser in actual use has changed in 0.3.x from the
`mw-parser.core` parser to the `mw-parser.declarative` parser. The API of the
parser is also substantially revised and is not backwards compatible, so if
you have code written to use 0.1.x versions of this library it will need to be
modified. I apologise for this. On the upside, the new parser API is much
simpler.
## Usage ## Usage
Main entry point is (parse-rule _string_), where string takes a form detailed Main entry point is (compile _string_), where string takes a form detailed
in __[grammar](#grammar)__, below. If the rule is interpretted correctly the result will in __[grammar](#grammar)__, below. If the rules represnted by the string are
be the source code of a Clojure anonymous function; if the rule cannot be interpretted, interpretted correctly, the result will be a a list of compiled Clojure
an error 'I did not understand...' will be shown. anonymous functions; if the rule cannot be interpretted, an error 'I did not
understand...' will be thrown.
The function (compile-rule _string_) is like parse-rule, except that it returns Each of these functions will have metadata including:
a compiled Clojure anonymous function.
* `:rule-type` : the type of rule the function represents;
* `:lisp` : the lisp source from which the function was compiled;
* `:parse` : the parse-tree from which that lisp source was derived;
* `:source` : the rule source from which the parse-tree was derived;
* `:line : the one-based line number of the rule source in the source file.
The values of `:rule-type` currently supported are:
* `:production` : an if-then rule which transforms the properties of a single
cell, based on the values of properties of that cell and optionally of its
neighbours;
* `:flow` : a flow rule which creates flows of values of a numeric property
from one cell to other cells.
Values which it is intended will be supported include rules to create graphs
which will enable the user to aggregate and interpret what is happening in
the world. Types which it is envisaged will be supported include
`:time-series`, `bar-graph` and perhaps others, but grammar for these has not
yet been developed.
### Generated function and evaluation environment ### Generated function and evaluation environment

View file

@ -32,6 +32,11 @@
;;;; ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; TODO: Either, when I first wrote this parser, I didn't adequately read the
;;; Instaparse documentation, or Instaparse has advanced considerably since
;;; then. Reading the documentation now, I could probably rewrite this to
;;; eliminate the simplify step altogether, and that would be well worth doing.
(def ruleset-grammar (def ruleset-grammar
"Experimental: parse a whole file in one go." "Experimental: parse a whole file in one go."
(join "\n" ["LINES := LINE | LINE CR LINES;" (join "\n" ["LINES := LINE | LINE CR LINES;"

View file

@ -1,7 +1,9 @@
(ns ^{:doc "Generate Clojure source from simplified parse trees." (ns ^{:doc "Generate Clojure source from simplified parse trees."
:author "Simon Brooke"} :author "Simon Brooke"}
mw-parser.generate mw-parser.generate
(:require [mw-parser.utils :refer [assert-type search-tree TODO]])) (:require
[mw-engine.utils :refer :all] ;; may need these when macro-expanding rules.
[mw-parser.utils :refer [assert-type search-tree TODO]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
@ -131,7 +133,9 @@
(case expression-type (case expression-type
:DISJUNCT-EXPRESSION (generate-disjunct-property-condition tree property qualifier expression) :DISJUNCT-EXPRESSION (generate-disjunct-property-condition tree property qualifier expression)
:RANGE-EXPRESSION (generate-ranged-property-condition tree property expression) :RANGE-EXPRESSION (generate-ranged-property-condition tree property expression)
(list qualifier (list property 'cell) expression))))) (list qualifier (if (number? expression)
(list 'mw-engine.utils/get-num 'cell property)
(list property 'cell)) expression)))))
(defn generate-qualifier (defn generate-qualifier
"From this `tree`, assumed to be a syntactically correct qualifier, "From this `tree`, assumed to be a syntactically correct qualifier,
@ -161,6 +165,21 @@
(generate others)) (generate others))
{property expression}))))) {property expression})))))
(defn trap-errors-in-dice-throw
"We're getting a wierd -- many would say 'impossible' -- intermittent bug
which appears to happen here. "
[sides chances action]
;; (list 'try
(list 'if (list '< (list 'rand sides) chances) action)
;; (list 'catch 'Exception 'any
;; (list 'println (list 'format "Dice throw bug %d/%d" chances sides))
;; (list 'throw (list 'ex-info "Error in dice throw"
;; {:total sides
;; :chances chances
;; :action action}
;; 'any))))
)
(defn generate-probable-action (defn generate-probable-action
"From this `tree`, assumed to be a syntactically correct probable action, "From this `tree`, assumed to be a syntactically correct probable action,
generate and return the appropriate clojure fragment." generate and return the appropriate clojure fragment."
@ -174,9 +193,7 @@
total (generate (nth tree 2)) total (generate (nth tree 2))
action (generate-action (nth tree 3) others)] action (generate-action (nth tree 3) others)]
;; TODO: could almost certainly be done better with macro syntax ;; TODO: could almost certainly be done better with macro syntax
(list 'if (trap-errors-in-dice-throw total chances action))))
(list '< (list 'rand total) chances)
action))))
(defn generate-action (defn generate-action
"From this `tree`, assumed to be a syntactically correct action, "From this `tree`, assumed to be a syntactically correct action,
@ -211,10 +228,10 @@
(assert-type tree :NUMERIC-EXPRESSION) (assert-type tree :NUMERIC-EXPRESSION)
(case (count tree) (case (count tree)
4 (let [[p operator expression] (rest tree) 4 (let [[p operator expression] (rest tree)
property (if (number? p) p (list p 'cell))] property (if (number? p) p (list 'mw-engine.utils/get-num 'cell p))]
(list (generate operator) (generate property) (generate expression))) (list (generate operator) (generate property) (generate expression)))
(case (first (second tree)) (case (first (second tree))
:SYMBOL (list (keyword (second (second tree))) 'cell) :SYMBOL (list 'mw-engine.utils/get-num 'cell (generate (second tree)))
(generate (second tree))))) (generate (second tree)))))
(defn generate-neighbours-condition (defn generate-neighbours-condition