Using QuasiQuotation for more readable Atom code

Lately I’ve been experimenting with using Atom to generate intrinsically reliable embedded software.

In contrast to alternatives like Copilot in Atom you include the surrounding C code as Haskell strings whereas other concepts just generate a set of C files to include in your main code.

However, inclusion of the C code as String leads to constructs like this:

footer :: String
footer = unlines [
          "void setup() {",
          "  pinMode(ledPin, OUTPUT);",
          "}"]

When modifying the C code you always have to take care that not only the C code itself needs to be correct, but you also have to maintain the list-of-lines construct surrounding it.

I believe this is prone to errors and even if editors might support you in doing so, the code suffers from lack of reliability.

Using GHC’s QuasiQuotation as described on the HaskellWiki, the FPComplete TemplateHaskell 101 and the FPComplete QuasiQuotation 101 I found an easy solution for including pure C code in Haskell as Strings.

Prerequisites:

First, you need to add a Haskell module providing the QuasiQuoter for string embedding (note that GHC disallows the QuasiQuoter to be declared locally, so it needs to be in a separate module).

Credits go to the Author of the FPComplete TH 101 for providing an example that helped me getting started. His code, however, produces a warning about some fields of QuasiQuoter being undefined, therefore I added undefined for said fields.

module StringEmbed(embedStr, embedStrFile) where
 
import Language.Haskell.TH
import Language.Haskell.TH.Quote

embedStr :: QuasiQuoter 
embedStr = QuasiQuoter { quoteExp = stringE,
                    quotePat = undefined,
                    quoteDec = undefined,
                    quoteType = undefined }

embedStrFile :: QuasiQuoter
embedStrFile = quoteFile embedStr

Including C code inline:

In order to use inline C code, simply import StringEmbed and then enclose your C code like this: [embedStr|...|]

{-# LANGUAGE QuasiQuotes #-}

import StringEmbed

cFooter :: String
cFooter = [embedStr|

void setup() {
  pinMode(ledPin, OUTPUT);
}

|]

Include C code from external files:

If you have large amounts of C code to include, consider the possibility of including it from external files to un-clutter the code. StringEmbed.hs already contains embedStringFile allowing you to easily use this possibility:

{-# LANGUAGE QuasiQuotes #-}

import StringEmbed

cFooter :: String
cFooter = [embedStrFile|code.c|]

An alternative way would be to use embedFile from the file-embed library:

{-# LANGUAGE TemplateHaskell #-}
import Data.FileEmbed
import Data.ByteString (ByteString)

cFooter :: ByteString
cFooter = $(embedFile "code.c")

Note that embedFile yields a ByteString instead of a String. While this is generally more efficient, in case of Atom only the compilation efficiency is affected and you have to convert the ByteString to String anyway in order to use it in Atom.