cucumber-attic / cucumber-jvm-clojure

Cucumber Clojure
MIT License
29 stars 10 forks source link

Macro expansion in Clojure step defs #3

Open mpkorstanje opened 6 years ago

mpkorstanje commented 6 years ago

From: https://github.com/cucumber/cucumber-jvm/issues/631

I am trying to use a macro in a step definition, and instead of having access to the value passed in to my step definition I get a symbol.

This is perhaps nothing more than me being relatively new to Clojure but it's certainly unexpected.

Given the the following scenario:

Scenario: Using macros inside step defintions
  Given my words are replaced:
  """
  My word is %{word}
  """

When I run my cukes with a step defintion like:

;; Step definition
(defmacro fmt [^String string]
  (let [-re #"%\{(.*?)\}"
        fstr (clojure.string/replace string -re "%s")
        fargs (map #(read-string (second %)) (re-seq -re string))]
    `(format ~fstr ~@fargs)))

(Given #"my words are replaced:" [string]
  (let [word "interpolated"]
    (assert (= (fmt string) "My word is interpolated")))

Then I get an error of:

Caused by: java.lang.ClassCastException: clojure.lang.Symbol cannot be cast to java.lang.CharSequence
    at clojure.core$re_matcher.invoke(core.clj:4386)
    at clojure.core$re_seq.invoke(core.clj:4411)
    at cucumber.runtime.clj$fmt.invoke(api_steps.clj:18)
    at clojure.lang.Var.invoke(Var.java:423)
    at clojure.lang.AFn.applyToHelper(AFn.java:167)
    at clojure.lang.Var.applyTo(Var.java:532)
    at clojure.lang.Compiler.macroexpand1(Compiler.java:6468)
    at clojure.lang.Compiler.analyzeSeq(Compiler.java:6546)

I've gisted the full backtrace if that's of use, and I'm running cukes via lein-cucumber.

[lein-cucumber "1.0.2"]
  [info.cukes/cucumber-clojure "1.1.1"]
    [info.cukes/cucumber-core "1.1.1"]

If this is just me struggling at 11pm I apologise.

As a workaround I'm using a function instead of a macro, and passing the interpolations in:

(defn- fmt [^String string interpolations]
  (let [pattern #"%\{(.*?)\}"
        f (fn [[_ k]] (get interpolations k :missing))]
    (clojure.string/replace string pattern f)))

Without the macro all is well.