Having a package visible in the environment in which generated code is compiled?

56 views
Skip to first unread message

Simon Brooke

unread,
Jul 5, 2014, 9:10:33 AM7/5/14
to clo...@googlegroups.com
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!

Simon Brooke

unread,
Jul 7, 2014, 5:26:43 PM7/7/14
to clo...@googlegroups.com
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!
Reply all
Reply to author
Forward
0 new messages