bookhound-0.1.4.0: src/Parsers/Yaml.hs
module Parsers.Yaml (yaml, nil, integer, float, bool, string,
list, mapping) where
import SyntaxTrees.Yaml (YamlExpression(..), CollectionType(..))
import Parser(Parser, check, andThen, exactly)
import ParserCombinators (IsMatch(..), (<|>), (<#>), (>>>), (|?), (|*), (|+), (|++), maybeWithin)
import Parsers.Number (double, hexInt, int, octInt)
import Parsers.String (spaces, spacesOrTabs, withinDoubleQuotes, withinQuotes,
blankLine, blankLines, tabs)
import Parsers.Char (colon, dash, space, whiteSpace, newLine, question, dot, hashTag,
quote, doubleQuote, char)
import Parsers.Collections (mapOf, listOf)
import qualified Parsers.DateTime as Dt
import qualified Data.Map as Map
import Data.List (nub)
yaml :: Parser YamlExpression
yaml = normalize `andThen` yamlWithIndent (-1)
-- TODO: Add support for anchors and aliases
nil :: Parser YamlExpression
nil = YamlNull <$ oneOf ["null", "Null", "NULL"]
integer :: Parser YamlExpression
integer = YamlInteger <$> (hexInt <|> octInt <|> int)
float :: Parser YamlExpression
float = YamlFloat <$> double
bool :: Parser YamlExpression
bool = YamlBool <$> (True <$ oneOf ["true", "True", "TRUE"] <|>
False <$ oneOf ["false", "False", "FALSE"])
dateTime :: Parser YamlExpression
dateTime = YamlDateTime <$> Dt.dateTime
date :: Parser YamlExpression
date = YamlDate <$> Dt.date
time :: Parser YamlExpression
time = YamlTime <$> Dt.time
string :: Int -> Parser YamlExpression
string indent = YamlString <$> text indent
sequential :: Parser a -> Int -> Parser [YamlExpression]
sequential sep indent = listParser where
listParser = indentationCheck elemParser indent
elemParser = do n <- length <$> (space |*)
sep *> whiteSpace
elm <- yamlWithIndent n
pure (n, elm)
list :: Int -> Parser YamlExpression
list indent = (YamlList Inline <$> jsonList) <|>
(YamlList Standard <$> yamlList) where
yamlList = sequential dash indent
jsonList = maybeWithin spacesOrTabs $ listOf $ yamlWithIndent (-1)
set :: Int -> Parser YamlExpression
set indent = YamlList Standard . nub <$> sequential question indent
mapping :: Int -> Parser YamlExpression
mapping indent = (YamlMap Inline <$> jsonMap) <|>
(YamlMap Standard <$> yamlMap) where
yamlMap = Map.fromList <$> mapParser
jsonMap = maybeWithin spacesOrTabs $ mapOf colon (text 100) $ yamlWithIndent (-1)
mapParser = indentationCheck keyValueParser indent
keyValueParser = do n <- length <$> (space |*)
key <- show <$> element indent
(spacesOrTabs |?)
colon *> whiteSpace
value <- yamlWithIndent n
pure (n, (key, value))
element :: Int -> Parser YamlExpression
element indent = exactly (dateTime <|> date <|> time <|> float <|>
integer <|> bool <|> nil) <|>
string indent
container :: Int -> Parser YamlExpression
container indent = list indent <|> mapping indent <|> set indent
yamlWithIndent :: Int -> Parser YamlExpression
yamlWithIndent indent = maybeWithin ((blankLine <|> comment <|> directive <|>
docStart <|> docEnd) |+)
yamlValue where
yamlValue = container indent <|> maybeWithin spacesOrTabs (element indent)
comment = hashTag *> (inverse newLine |+) <* newLine
directive = is "%" *> (inverse space *> (inverse newLine |+)) <* newLine
docStart = dash <#> 3
docEnd = dot <#> 3
text :: Int -> Parser String
text indent = withinDoubleQuotes (quotedParser (inverse doubleQuote |*)) <|>
withinQuotes (quotedParser (inverse quote |*)) <|>
(is "|" *> blankLine *> plainTextParser literalLineParser) <|>
(is ">" *> blankLine *> (spacesOrTabs |?) *> plainTextParser foldingLineParser) <|>
plainTextParser foldingLineParser
where
quotedParser parser = mconcat <$> ((snd <$> foldingLineParser parser) |*)
plainTextParser styleParser = allowedStart >>> allowedString >>>
(indentationCheck (styleParser allowedString) indent |*)
foldingLineParser parser = do sep <- ("\n" <$ newLine <* blankLines) <|> (" " <$ newLine)
n <- maybeWithin tabs $ length <$> (space |*)
str <- parser
pure (n, sep ++ str)
literalLineParser parser = do sep <- pure <$> newLine
n <- length <$> (space |*)
str <- parser
pure (n, sep ++ replicate (n - indent) ' ' ++ str)
allowedStart = noneOf $ forbiddenChar ++ ['>', '|', ':', '!']
allowedString = (noneOf forbiddenChar |*)
forbiddenChar = ['\n', '#', '&', '*', ',', '?', '-', ':', '[', ']', '{', '}']
indentationCheck :: Parser (Int, a) -> Int -> Parser [a]
indentationCheck parser indent = ((snd <$> check "indentation"
(\(n, _) -> n > indent) parser) |+)
normalize :: Parser String
normalize = (parserActions >>> normalize) <|> (char |*) where
parserActions = spreadDashes <|>
spreadDashKey <|>
spreadKeyDash <|>
next
next = pure <$> char
spreadDashes = (++ "- ") . genDashes <$> dashesParser
genDashes (offset, n) = concatMap (\x -> "- " ++ replicate (offset + 2 * x) ' ')
[1 .. n - 1]
dashesParser = do offset <- length <$> (spaces |?)
n <- length <$> ((dash <* spacesOrTabs) |++)
pure (offset, n)
spreadDashKey = (\(offset, key) -> replicate offset ' ' ++ "- " ++
replicate (offset + 2) ' ' ++ key ++ ": ")
<$> dashKeyParser
dashKeyParser = do offset <- length <$> (spaces |?)
dash <* spacesOrTabs
key <- text 100 <* maybeWithin spacesOrTabs colon
pure (offset, key)
spreadKeyDash = (\(offset, key) -> replicate offset ' ' ++ key ++ ": " ++
replicate (offset + 2) ' ' ++ "- ")
<$> keyDashParser
keyDashParser = do offset <- length <$> (spaces |?)
key <- text 100 <* maybeWithin spacesOrTabs colon
dash <* spacesOrTabs
pure (offset, key)