Skip to content

Commit af9d9f9

Browse files
authored
Add TOML import (#50)
* LTS 12.10 * Initial TOML source * Wire into driver
1 parent 4e4183c commit af9d9f9

File tree

8 files changed

+137
-7
lines changed

8 files changed

+137
-7
lines changed

package.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ library:
5252
- split
5353
- strict
5454
- text
55+
- tomland
5556
- transformers
5657
- unix
5758
- unordered-containers
@@ -100,6 +101,7 @@ tests:
100101
- pretty
101102
- scientific
102103
- text
104+
- tomland
103105
- unordered-containers
104106
- vector
105107
- yaml

src/Eucalypt/Driver/Evaluator.hs

+5-2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import qualified Eucalypt.Driver.Stg as STG
4848
import Eucalypt.Reporting.Error (EucalyptError(..))
4949
import Eucalypt.Reporting.Report (reportErrors)
5050
import Eucalypt.Source.Error (DataParseException(..))
51+
import Eucalypt.Source.TomlSource
5152
import Eucalypt.Source.YamlSource
5253
import Eucalypt.Syntax.Ast (Unit, Expression)
5354
import Eucalypt.Syntax.Error (SyntaxError(..))
@@ -112,8 +113,9 @@ parseInputToCore :: Input -> IO (Either EucalyptError TranslationUnit)
112113
parseInputToCore i@(Input locator name format) = do
113114
source <- readInput locator
114115
case format of
116+
"toml" -> tomlDataToCore source
115117
"yaml" -> activeYamlToCore source
116-
"json" -> dataToCore source
118+
"json" -> yamlDataToCore source
117119
"eu" -> eucalyptToCore source
118120
_ -> (return . Left . Command . InvalidInput) i
119121
where
@@ -122,11 +124,12 @@ parseInputToCore i@(Input locator name format) = do
122124
case parseEucalypt text (show locator) of
123125
Left e -> (return . Left . Syntax) e
124126
Right expr -> (return . Right . maybeApplyName . translateToCore) expr
125-
dataToCore text = do
127+
yamlDataToCore text = do
126128
r <- try (parseYamlData text) :: IO (Either DataParseException CoreExpr)
127129
case r of
128130
Left e -> (return . Left . Source) e
129131
Right core -> (return . Right . maybeApplyName . dataUnit) core
132+
tomlDataToCore text = parseTomlData text >>= (return . Right <$> dataUnit)
130133
activeYamlToCore text = do
131134
r <- try (parseYamlExpr text) :: IO (Either DataParseException CoreExpr)
132135
case r of

src/Eucalypt/Source/Error.hs

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ data DataParseException
1010
= UnexpectedEndOfEvents
1111
| UnexpectedEvent Event
1212
| FromYamlException Text
13+
| FromTomlException Text
1314
deriving (Show, Typeable)
1415

1516
instance Exception DataParseException

src/Eucalypt/Source/TomlSource.hs

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
{-# LANGUAGE GADTs #-}
2+
{-# LANGUAGE TypeFamilies #-}
3+
{-# LANGUAGE RecordWildCards #-}
4+
{-|
5+
Module : Eucalypt.Source.TomlSource
6+
Description : Ingest TOML into core syntax
7+
Copyright : (c) Greg Hawkins, 2018
8+
License :
9+
Maintainer : [email protected]
10+
Stability : experimental
11+
-}
12+
module Eucalypt.Source.TomlSource where
13+
14+
import Control.Exception.Safe
15+
import qualified Data.ByteString as BS
16+
import qualified Data.HashMap.Strict as HM
17+
import Data.List (intercalate)
18+
import qualified Data.List.NonEmpty as NonEmpty
19+
import Data.Text (unpack)
20+
import Data.Text.Encoding (decodeUtf8)
21+
import Eucalypt.Core.Syn
22+
import Eucalypt.Source.Error
23+
import qualified Toml
24+
25+
-- | Convert a TOML primitive to a core expression
26+
--
27+
tomlValue :: Toml.Value t -> CoreExpr
28+
tomlValue (Toml.Bool b) = corebool b
29+
tomlValue (Toml.Integer n) = int n
30+
tomlValue (Toml.Double d) = float d
31+
tomlValue (Toml.Text s) = (str . unpack) s
32+
tomlValue (Toml.Date d) =
33+
withMeta
34+
(block [element "toml" $ block [element "type" $ sym "date"]])
35+
(str $ show d)
36+
tomlValue (Toml.Array a) = CoreList $ map tomlValue a
37+
38+
pieceToBindingName :: Toml.Piece -> CoreBindingName
39+
pieceToBindingName = unpack . Toml.unPiece
40+
41+
keyToBindingName :: Toml.Key -> CoreBindingName
42+
keyToBindingName (Toml.Key k) =
43+
intercalate "." $ map pieceToBindingName (NonEmpty.toList k)
44+
45+
-- | Translate a prefix tree into a list of blocks that can be
46+
-- concatenated
47+
translatePrefixTree :: Toml.PrefixTree Toml.TOML -> CoreExpr
48+
translatePrefixTree (Toml.Leaf k a) =
49+
inPrefixBlocks k $ translateToml a
50+
translatePrefixTree Toml.Branch {..} =
51+
inPrefixBlocks bCommonPref $ translatePrefixMap bPrefixMap
52+
53+
-- | Translate a prefix map
54+
translatePrefixMap :: Toml.PrefixMap Toml.TOML -> CoreExpr
55+
translatePrefixMap m =
56+
block
57+
[ element (pieceToBindingName k) $ translatePrefixTree v
58+
| (k, v) <- HM.toList m
59+
]
60+
61+
-- | Return expression wrapped in enough blocks to represent the
62+
-- prefix
63+
inPrefixBlocks :: Toml.Prefix -> CoreExpr -> CoreExpr
64+
inPrefixBlocks k ex = foldr wrap ex names
65+
where
66+
wrap l r = block [element l r]
67+
names = map pieceToBindingName (NonEmpty.toList . Toml.unKey $ k)
68+
69+
-- | Translate a TOML file into a 'CoreExpr'
70+
--
71+
translateToml :: Toml.TOML -> CoreExpr
72+
translateToml Toml.TOML {..} = foldl1 collapse (pairBlocks ++ tables)
73+
where
74+
pairBlocks = map kvBlock $ HM.toList tomlPairs
75+
tables = map translatePrefixTree $ HM.elems tomlTables
76+
kvBlock (k, Toml.AnyValue val) = inPrefixBlocks k $ tomlValue val
77+
collapse (CoreBlock (CoreList l)) (CoreBlock (CoreList r)) =
78+
CoreBlock . CoreList $ l ++ r
79+
collapse _ _ = error "Collapsing non-block expressions"
80+
81+
-- | Parse inert TOML data into a CoreExpr
82+
parseTomlData :: BS.ByteString -> IO CoreExpr
83+
parseTomlData src =
84+
case Toml.parse . decodeUtf8 $ src of
85+
Left (Toml.ParseException t) -> throwM $ FromTomlException t
86+
Right val -> return . translateToml $ val

src/Eucalypt/Syntax/Input.hs

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ inferFormat loc =
7272
extToFormat ext =
7373
case ext of
7474
".json" -> Just "json"
75+
".toml" -> Just "toml"
7576
".yaml" -> Just "yaml"
7677
".yml" -> Just "yaml"
7778
".eu" -> Just "eu"

stack.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
resolver: lts-12.9
1+
resolver: lts-12.10
22
packages:
33
- '.'
44
ghc-options:
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
3+
module Eucalypt.Source.TomlSourceSpec
4+
( main
5+
, spec
6+
) where
7+
8+
import Eucalypt.Core.Syn
9+
import Eucalypt.Source.TomlSource
10+
import Test.Hspec
11+
12+
main :: IO ()
13+
main = hspec spec
14+
15+
spec :: Spec
16+
spec =
17+
describe "Toml parser" $
18+
it "parses basic toml snippet" $
19+
parseTomlData "foo = \"bar\"\n\n[a]\nx=3\ny = 4\nz.p = 12\n[r.s.t]\nf.f=8\n" `shouldReturn`
20+
block
21+
[ element "foo" $ str "bar"
22+
, element "a" $
23+
block
24+
[ element "x" $ int 3
25+
, element "z" $ block [element "p" $ int 12]
26+
, element "y" $ int 4
27+
]
28+
, element "r" $
29+
block
30+
[ element "s" $
31+
block
32+
[element "t" $ block [element "f" $ block [element "f" $ int 8]]]
33+
]
34+
]

test/Eucalypt/Syntax/InputSpec.hs

+7-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ import Eucalypt.Syntax.Input
88

99
spec :: Spec
1010
spec = do
11-
describe "inferFormat" $
12-
it "respects extension" $
13-
(inferFormat . URLInput . fromJust . parseRelativeReference) "data.json" `shouldBe`
14-
Just "json"
11+
describe "infer format" $ do
12+
it "recognises json" $
13+
(inferFormat . URLInput . fromJust . parseRelativeReference) "data.json" `shouldBe`
14+
Just "json"
15+
it "recognises toml" $
16+
(inferFormat . URLInput . fromJust . parseRelativeReference) "data.toml" `shouldBe`
17+
Just "toml"
1518
describe "parseInput" $ do
1619
it "parses simple.eu" $
1720
parseInputFromString "simple.eu" `shouldBe`

0 commit comments

Comments
 (0)