1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
|
# 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]: Toml.Lexer
[Token] --> [Expr]: Toml.Parser
[Expr] --> Table : Toml.Semantics
Table --> ApplicationTypes : Toml.FromValue
ApplicationTypes --> Table : Toml.ToValue
Table --> TOML : Toml.Pretty
```
The highest-level interface to this package is to define `FromValue` and `ToTable`
instances for your application-specific datatypes. These can be used with `encode`
and `decode` to convert to and from TOML.
For low-level access to the TOML format, the lexer, parser, and validator are available
for direct use. The diagram above shows how the different modules enable you to
advance through the increasingly high-level TOML representations.
## 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
import GHC.Generics (Generic)
import QuoteStr (quoteStr)
import Test.Hspec (Spec, hspec, it, shouldBe)
import Toml (parse, decode, encode, Value(..))
import Toml.FromValue (Result(Success), FromValue(fromValue), parseTableFromValue, reqKey)
import Toml.Generic (GenericTomlTable(..))
import Toml.ToValue (ToValue(toValue), ToTable(toTable), defaultTableToValue, table, (.=))
main :: IO ()
main = hspec (parses >> decodes >> encodes)
```
### Using the raw parser
Consider this sample TOML text from the TOML specification.
```haskell
fruitStr :: String
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 value
```haskell
parses :: Spec
parses = it "parses" $
parse fruitStr
`shouldBe`
Right (table [
("fruits", Array [
Table (table [
("name", String "apple"),
("physical", Table (table [
("color", String "red"),
("shape", String "round")])),
("varieties", Array [
Table (table [("name", String "red delicious")]),
Table (table [("name", String "granny smith")])])]),
Table (table [
("name", String "banana"),
("varieties", Array [
Table (table [("name", String "plantain")])])])])])
```
### Using decoding classes
Here's an example of defining datatypes and deserializers for the TOML above.
The `FromValue` typeclass is used to encode each datatype into a TOML value.
Instances can be derived for simple record types. More complex examples can
be manually derived.
```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)
newtype Variety = Variety String
deriving (Eq, Show)
instance FromValue Physical where
fromValue = parseTableFromValue (Physical <$> reqKey "color" <*> reqKey "shape")
instance FromValue Variety where
fromValue = parseTableFromValue (Variety <$> reqKey "name")
```
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"]])
```
### Using encoding classes
The `ToValue` class is for all datatypes that can be encoded into TOML.
The more specialized `ToTable` class is for datatypes that encode into
tables and are thus eligible to be top-level types (all TOML documents
are tables at the top-level).
Generics can be used to derive `ToTable` for simple record types.
Manually defined instances are available for the more complex cases.
```haskell
instance ToValue Physical where toValue = defaultTableToValue
instance ToTable Physical where toTable x = table ["color" .= color x, "shape" .= shape x]
instance ToValue Variety where toValue = defaultTableToValue
instance ToTable Variety where toTable (Variety x) = table ["name" .= x]
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"|]
```
## 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).
|