Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/glguy/toml-parser

Haskell parser and printer for the TOML 1.0.0 file format
https://github.com/glguy/toml-parser

haskell toml

Last synced: 3 months ago
JSON representation

Haskell parser and printer for the TOML 1.0.0 file format

Awesome Lists containing this project

README

        

# TOML Parser

This package implements a validating parser for [TOML 1.0.0](https://toml.io/en/v1.0.0).

This package uses an [alex](https://haskell-alex.readthedocs.io/en/latest/)-generated
lexer and [happy](https://haskell-happy.readthedocs.io/en/latest/)-generated parser.

It also provides a pair of classes for serializing into and out of TOML.

## Package Structure

```mermaid
---
title: Package Structure
---
stateDiagram-v2
classDef important font-weight:bold;

TOML:::important --> ApplicationTypes:::important : decode
ApplicationTypes --> TOML : encode
TOML --> [Token]: Lexer
[Token] --> [Expr]: Parser
[Expr] --> Table : Semantics
Table --> ApplicationTypes : FromValue
ApplicationTypes --> Table : ToValue
Table --> TOML : Pretty
```

Most users will only need to import **Toml** or **Toml.Schema**. Other top-level
modules are for low-level hacking on the TOML format itself. All modules below
these top-level modules are exposed to provide direct access to library implementation
details.

- **Toml** - Basic encoding and decoding TOML
- **Toml.Schema** - TOML schemas for application types
- **Toml.Semantics** - Low-level semantic operations on TOML syntax
- **Toml.Syntax** - Low-level parsing of text into TOML raw syntax

## Examples

This file uses [markdown-unlit](https://hackage.haskell.org/package/markdown-unlit)
to ensure that its code typechecks and stays in sync with the rest of the package.

```haskell
{-# Language OverloadedStrings #-}
import Data.Text (Text)
import GHC.Generics (Generic)
import QuoteStr (quoteStr)
import Test.Hspec (Spec, hspec, it, shouldBe)
import Toml
import Toml.Schema

main :: IO ()
main = hspec (parses >> decodes >> encodes >> warns >> errors)
```

### Using the raw parser

Consider this sample TOML text from the TOML specification.

```haskell
fruitStr :: Text
fruitStr = [quoteStr|
```

```toml
[[fruits]]
name = "apple"

[fruits.physical] # subtable
color = "red"
shape = "round"

[[fruits.varieties]] # nested array of tables
name = "red delicious"

[[fruits.varieties]]
name = "granny smith"

[[fruits]]
name = "banana"

[[fruits.varieties]]
name = "plantain"
```

```haskell
|]
```

Parsing using this package generates the following unstructured value

```haskell
parses :: Spec
parses = it "parses" $
forgetTableAnns <$> parse fruitStr
`shouldBe`
Right (table [
("fruits", List [
Table (table [
("name", Text "apple"),
("physical", Table (table [
("color", Text "red"),
("shape", Text "round")])),
("varieties", List [
Table (table [("name", Text "red delicious")]),
Table (table [("name", Text "granny smith")])])]),
Table (table [
("name", Text "banana"),
("varieties", List [
Table (table [("name", Text "plantain")])])])])])
```

### Defining a schema

We can define a schema for our TOML format in the form of instances of
`FromValue`, `ToValue`, and `ToTable` in order to read TOML directly
into structured data form. This example manually derives some of the
instances as a demonstration.

```haskell
newtype Fruits = Fruits { fruits :: [Fruit] }
deriving (Eq, Show, Generic)
deriving (ToTable, ToValue, FromValue) via GenericTomlTable Fruits

data Fruit = Fruit { name :: String, physical :: Maybe Physical, varieties :: [Variety] }
deriving (Eq, Show, Generic)
deriving (ToTable, ToValue, FromValue) via GenericTomlTable Fruit

data Physical = Physical { color :: String, shape :: String }
deriving (Eq, Show, Generic)
deriving (ToTable, ToValue, FromValue) via GenericTomlTable Physical

newtype Variety = Variety String
deriving (Eq, Show)

instance FromValue Variety where
fromValue = parseTableFromValue (Variety <$> reqKey "name")
instance ToValue Variety where
toValue = defaultTableToValue
instance ToTable Variety where
toTable (Variety x) = table ["name" .= x]

```

We can run this example on the original value to deserialize it into domain-specific datatypes.

```haskell
decodes :: Spec
decodes = it "decodes" $
decode fruitStr
`shouldBe`
Success [] (Fruits [
Fruit
"apple"
(Just (Physical "red" "round"))
[Variety "red delicious", Variety "granny smith"],
Fruit "banana" Nothing [Variety "plantain"]])

encodes :: Spec
encodes = it "encodes" $
show (encode (Fruits [Fruit
"apple"
(Just (Physical "red" "round"))
[Variety "red delicious", Variety "granny smith"]]))
`shouldBe` [quoteStr|
[[fruits]]
name = "apple"

[fruits.physical]
color = "red"
shape = "round"

[[fruits.varieties]]
name = "red delicious"

[[fruits.varieties]]
name = "granny smith"|]
```

### Useful errors and warnings

This package takes care to preserve source information as much as possible
in order to provide useful feedback to users. These examples show a couple
of the message that can be generated when things don't go perfectly.

```haskell
warns :: Spec
warns = it "warns" $
decode [quoteStr|
name = "simulated"
typo = 10|]
`shouldBe`
Success
["2:1: unexpected key: typo in "] -- warnings
(Variety "simulated")

errors :: Spec
errors = it "errors" $
decode [quoteStr|
# Physical characteristics table
color = "blue"
shape = []|]
`shouldBe`
(Failure
["3:9: expected string but got array in shape"]
:: Result String Physical)
```

## More Examples

A demonstration of using this package at a more realistic scale
can be found in [HieDemoSpec](test/HieDemoSpec.hs). The various unit
test files demonstrate what you can do with this library and what
outputs you can expect.

See the low-level operations used to build a TOML syntax highlighter
in [TomlHighlighter](test-drivers/highlighter/Main.hs).