Answering myself, the solution has been to do a (use 'mw-engine.utils) in 
the immediate evaluation environment - just in the same file isn't enough. 
So I've written a function

(defn compile-rule 
  "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."
  [rule-text]
  (do
    (use 'mw-engine.utils)
    (eval (parse-rule rule-text))))  


This works!

On Saturday, 5 July 2014 14:10:33 UTC+1, Simon Brooke wrote:
>
> I am trying to write a domain-specific production rule language which is 
> compiled down at run-time into Clojure code. It's intended that the rule 
> language should be usable by primary school children with little help.
>
> So far it's going extremely well, except for one problem. If I run it in 
> the REPL, like this, it works:
>
>
> user=> (use 'mw-parser.core :reload)
> nil
> user=> (use 'mw-engine.utils)
> nil
> user=> (parse-rule "if state is forest and fertility is between 55 and 75 
> then state should be climax")
> (fn [cell world] (if (and (= (:state cell) :forest) (or (< 55 (get-int 
> cell :fertility) 75) (> 55 (get-int cell :fertility) 75))) (merge cell 
> {:state :climax})))
> user=> (eval *1)
> #<user$eval1839$fn__1840 user$eval1839$fn__1840@7a52b16b>
> user=> (apply *1 (list {:state :forest :fertility 60} nil))
> {:state :climax, :fertility 60}
> user=> 
>
>
> However, I have a test as follows:
>
> ns mw-parser.core-test
>   (:use mw-engine.utils)
>   (:require [clojure.test :refer :all]
>             [mw-parser.core :refer :all]))
>
> (deftest rules-tests
>   (testing "if altitude is less than 100 and state is forest then state 
> should be climax and deer should be 3"
>            (is (parse-rule "if altitude is less than 100 and state is 
> forest then state should be climax and deer should be 3"))
>            (is (let [cell (apply (eval (parse-rule "if altitude is less 
> than 100 and state is forest then state should be climax and deer should be 
> 3"))
>                                  (list {:state :forest :altitude 99} nil))]
>                  (and (= (:state cell) :climax) (= (:deer cell) 3))))
>            ))
>
> This fails as follows:
>
> simon@engraver:~/workspace/mw-parser$ lein test
>
> lein test mw-parser.core-test
>
> lein test :only mw-parser.core-test/rules-tests
>
> ERROR in (rules-tests) (Compiler.java:6380)
> if altitude is less than 100 and state is forest then state should be 
> climax and deer should be 3
> expected: (let [cell (apply (eval (parse-rule "if altitude is less than 
> 100 and state is forest then state should be climax and deer should be 3")) 
> (list {:state :forest, :altitude 99} nil))] (and (= (:state cell) :climax) 
> (= (:deer cell) 3)))
>   actual: clojure.lang.Compiler$CompilerException: 
> java.lang.RuntimeException: Unable to resolve symbol: get-int in this 
> context, compiling:(/tmp/form-init4592216274934008360.clj:1:6384)
>
>
> The function get-int is in mw-engine.utils, and is:
>
> (defn get-int
>   "Get the value of a property expected to be an integer from a map; if 
> not present (or not an integer) return 0.
>   
>    * `map` a map;
>    * `key` a symbol or keyword, presumed to be a key into the `map`."
>   [map key]
>   (cond map
>     (let [v (map key)]
>       (cond (and v (integer? v)) v
>             true 0))
>
>         true (throw (Exception. "No map passed?"))))
>
>  
> I'm trying to understand why this function is not available in the test 
> environment when the anonymous function generated from the rule text is 
> compiled and applied. This matters, because I expect the users of the 
> system to add rules via a web form, so they won't have a REPL. 
>
> As you can see I've specified that mw-engine.utils is used by the test 
> file, but that does not apparently make the namespace available in eval. It 
> is the eval step, not the apply step, that fails, I've verified that by 
> trying:
>
> user=> (use 'mw-parser.core :reload)
> nil
> user=> (parse-rule "if state is forest and fertility is between 55 and 75 
> then state should be climax")
> (fn [cell world] (if (and (= (:state cell) :forest) (or (< 55 (get-int 
> cell :fertility) 75) (> 55 (get-int cell :fertility) 75))) (merge cell 
> {:state :climax})))
> user=> (eval *1)
>
> CompilerException java.lang.RuntimeException: Unable to resolve symbol: 
> get-int in this context, 
> compiling:(/tmp/form-init2345285067501587397.clj:1:1) 
> user=> 
>
>
> (Note that on this occasion I didn't include the (use 'mw-engine.utils) 
> step)
>
> Any assistance gratefully received!
>
>

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to