Search code examples
clojurecore.match

How to use re-matches with case or core.match


I am trying to implement pattern-matching in Clojure. My preference is to use core.match to match on a given regex pattern. I tried this:

(defn markdown->html [markdown-line]
  (match [markdown-line]
    [(boolean (re-matches #"#\s+\w+" markdown-line))] (str "<h1>")))

This does not even compile properly. I pivoted to a case conditional:

(defn markdown->html [markdown-line]
  (case markdown-line
    (boolean (re-matches #"#\s+\w+" markdown-line)) (str "<h1>")))

However, that did not give me the expected results when I invoke it with this: (markdown->html "# Foo")

However, this works!

(defn markdown->html [markdown-line]
  (if
    (boolean (re-matches #"#\s+\w+" markdown-line)) (str "<h1>")))

For all the tests above, I am invoking the function like so: (markdown->html "# Foo")

Does anyone know what I am doing wrong?


Solution

  • See the docs for case:

    The test-constants are not evaluated. They must be compile-time literals, and need not be quoted.

    For example:

    (case 'y
      y "y"
      c "c"
      (x z) "x or z"
      (a b) "a or b"
      "default")
    

    And clojure.core.match/match is similar, so I'd say both are wrong tool for your problem.

    If you're trying to write function for converting Github markdown to HTML, check clojure.string/replace, which can help you:

    (clojure.string/replace "# Foo bar
    # Biz baz" #"#\s+([\w ]*)" (fn [[result group]] (str "<h1>" group "</h1>")))
    
    => "<h1>Foo bar</h1>\n<h1>Biz baz</h1>"
    

    Or even better, using $ for group:

    (clojure.string/replace "# Foo bar
    # Biz baz" #"#\s+([\w ]*)" "<h1>$1</h1>")
    
    => "<h1>Foo bar</h1>\n<h1>Biz baz</h1>"
    

    By the way, your example can be improved like this:

    (defn markdown->html [markdown-line]
      (when (re-matches #"#\s+\w+" markdown-line) "<h1>"))
    
    (markdown->html "# Foo")
    => "<h1>"
    

    If is missing else branch, so when is better; you don't have to use boolean, because false or nil are considered as logical false and any other value is logical true and there is no reason to wrap one string in str.

    EDIT: Function for headers <h1> - <h6>:

    (def text "# Foo bar
    ## Biz baz
    ### Foo baz
    ## Biz foo")
    
    (clojure.string/replace text
                            #"(#{1,6})\s+([\w ]*)"
                            (fn [[result group1 group2]]
                              (let [tag (str "h" (count group1) ">")]
                                (str "<" tag group2 "</" tag))))
    
    => "<h1>Foo bar</h1>\n<h2>Biz baz</h2>\n<h3>Foo baz</h3>\n<h2>Biz foo</h2>"