4

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?

simon1505475
  • 675
  • 3
  • 9

1 Answers1

5

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) |] }
Community
  • 1
  • 1
Alec
  • 31,829
  • 7
  • 67
  • 114
  • That's cool, thank you! It seems to work remarkably well. I assumed because parseExp is not in the Q monad that it might not handle scopes well but it does. The package information is not specific about what is lacking. Do you know more about that? – simon1505475 Dec 05 '16 at 21:51
  • 1
    @simon1505475 Yeah, that package is in need of some love. Under the hood it relies on `haskell-src-exts`, which gives you the ability to specify a `ParseMode`. `haskell-src-meta` probably uses [`defaultParseMode`](https://hackage.haskell.org/package/haskell-src-exts-1.19.0/docs/Language-Haskell-Exts-Parser.html#v:defaultParseMode) in which case "The default is an unknown filename, no extensions (i.e. Haskell 98), don't ignore LANGUAGE pragmas, do ignore LINE pragmas, and be aware of fixities from the Prelude." – Alec Dec 06 '16 at 07:22
  • 1
    @simon1505475 I take back my previous comment. [This is the parse mode used](https://github.com/bmillwood/haskell-src-meta/blob/master/src/Language/Haskell/Meta/Parse.hs#L70-L79) – Alec Dec 06 '16 at 07:29