Search code examples
haskelltemplate-haskell

How to call the quasiquoter for haskell syntax explicitly?


I'm building an eDSL on top of HaTeX. The problem I'm facing is that I want to display Haskell expressions in my LaTeX document and I want to use the same Haskell expression to help generate the document.

The obvious answer is to copy and paste the expression so that it appears both quoted and live. I want to avoid that because the expression is subject to change.

What I'm imagining is a quasi quoter that both splices its content as it stands and output a string that represents it.

For example, here is what I would like to type in:

document = do
    title "this is an example document"
    paragraph "This is normal text. We will now show the Haskell code that generates a table"
    [quoted| makeTable ["heading1","heading2"] ["cell1","cell2"] |]

and I would like the quasi quote to expand to:

document = do
    title "this is an example document"
    paragraph "This is normal text. We will now show the Haskell code that generates a table"
    makeTable ["heading1","heading2"] ["cell1","cell2"]
    listing Haskell "makeTable [\"heading1\",\"heading2\"] [\"cell1\",\"cell2\"]"

For this, I need to write a QuasiQuoter:

quoted :: QuasiQuoter
quoted = QuasiQuoter
     { quoteExp = \str -> [e| $(_ str) >> listing Haskell $(lift str) |] }

I am unsure what to replace the hole with in $(_ str). I need to replace it with the Haskell expression quasi quoter but I am unsure how to call it. If e was e :: QuasiQuoter I could fill the hole with $(quoteExp e str) but it unfortunately doesn't work. What should I fill it with?


Solution

  • Short answer: there is no easy way. This question is still was unanswered for a reason. String -> Q Exp for Haskell is tough. The best way is probably through haskell-src-meta which provides parsers which return Template Haskell AST. In particular the Language.Haskell.Meta.Parse module gives us parseExp :: String -> Either String Exp.

    import Language.Haskell.Meta.Parse (parseExp)
    
    quoted :: QuasiQuoter
    quoted = QuasiQuoter
         { quoteExp = \str ->
             case parseExp str of
               Left msg -> fail "Could not parse expression."
               Right exp -> [e| $(pure exp) >> listing Haskell $(liftString str) |] }