Parsing with Data.Yaml.Combinators, part 1

This entry is part 1 of 3 in the series Data.Yaml.Combinators

So, I need to make a parser. The input is YAML and I want to learn Haskell. The first hit I get is Data.Yaml, which sounds promising. But while searching for an example usage, I happen upon a blog post named Better YAML parsing, and who doesn’t want better, right? Alas, I’m a Haskell newbie and don’t even understand most of the operators used in the very terse example, so in building my own parser, there is a lot of trial and error and pondering the meaning of function types.

Writing this post has multiple purposes. My example could help others who want to use the excellent yaml-combinators package. Someone with a much better understanding of Haskell may read it and point out a better way to structure the code. I’m grateful for any comments which may further my understanding of Haskell or parser combinators.

Since my actual input is technical and boring, I will make up an example input for the purposes of this post. The example will be less technical, but equally boring:

persons:
- name: 'Coco Lenoix'
address: '1612 Havenhurst Drive'
- name: 'Diane Selwyn'
address: '2900 Griffith Park Boulevard'
organizations:
- name: 'StudioCanal'
address: '1 Place du Spectacle'
- name: 'ABC Studios'
address: '77 West 66th Street'
view raw input.yaml hosted with ❤ by GitHub

Don’t fret, this is just the start. I promise it will get more intricate.

We’ll model this like so:

module Contacts
( Contacts,
contactsParser
) where
import Data.Vector (Vector)
import Data.Text (Text)
import Data.Yaml.Combinators
data Contacts = Contacts
(Vector Person)
(Vector Organization)
deriving Show
data Person = Person
Text -- name
Text -- address
deriving Show
data Organization = Organization
Text -- name
Text -- address
deriving Show
view raw Contacts.hs hosted with ❤ by GitHub

We need to import Vector and Text of course. To make it clean we’ll stick it all in a module Contacts in the file Contacts.hs. Deriving Show makes it easy to print the address book in a fairly readable format.

We need a main function to read the file and initiate the parsing. We’ll stick that in Main.hs.

module Main where
import qualified Data.ByteString.Char8 as BS
import qualified Data.Yaml.Combinators as Y
import System.Environment (getArgs)
import System.IO
import Contacts
main :: IO ()
main = do
args <- getArgs
bs <- BS.readFile $ head args
let parsedContent = Y.parse contactsParser bs :: Either String Contacts
case parsedContent of
(Left e) -> error e
(Right ts) -> putStrLn $ show ts
view raw Main.hs hosted with ❤ by GitHub

Now we need to slip in that contactsParser. We’ll put that in the Contacts.hs file.

contactsParser :: Parser Contacts
contactsParser = object $ Contacts
<$> field "persons" personListParser
<*> field "organizations" organizationListParser
personListParser :: Parser (Vector Person)
personListParser = array personParser
organizationListParser :: Parser (Vector Organization)
organizationListParser = array organizationParser
personParser :: Parser Person
personParser = object $ Person
<$> field "name" string
<*> field "address" string
organizationParser :: Parser Organization
organizationParser = object $ Organization
<$> field "name" string
<*> field "address" string
view raw Contacts.hs hosted with ❤ by GitHub

Trying to build this we get an error:

 • Couldn’t match expected type ‘Text’ with actual type ‘[Char]’

This is because yaml-combinators wants Text as field names, but we provide [Char]. Instead of mucking about with Text constructors at every string literal we’ll just activate the OverloadedStrings extension. And of course we need to import Data.Yaml.Combinators.

{-# LANGUAGE OverloadedStrings #-}
module Contacts
( Contacts,
contactsParser
) where
import Data.Vector (Vector)
import Data.Text (Text)
import Data.Yaml.Combinators
view raw Contacts.hs hosted with ❤ by GitHub

We build and run it.

 > contacts-parser input.yaml
Contacts [Person “Coco Lenoix” “1612 Havenhurst Drive”,Person “Diane Selwyn” “2900 Griffith Park Boulevard”] [Organization “StudioCanal” “1 Place du Spectacle”,Organization “ABC Studios” “77 West 66th Street”]

This part was pretty straight forward. Without understanding the meaning of <$> and <*>, I could pretty much whip it up based on the example from the Better YAML parsing article. All fields were parsed from string to Text. In the next post, I will show how to parse from string to a type of your own.

Series NavigationParsing with Data.Yaml.Combinators, part 2 >>

Leave a Reply

Your email address will not be published.