diff --git a/src/KDL/Decoder/Arrow.hs b/src/KDL/Decoder/Arrow.hs index c5bf769..6479c35 100644 --- a/src/KDL/Decoder/Arrow.hs +++ b/src/KDL/Decoder/Arrow.hs @@ -128,6 +128,7 @@ import KDL.Types ( NodeList (..), Value (..), ValueData (..), + def, ) import Numeric.Natural (Natural) import Prelude hiding (any, fail, null) @@ -627,7 +628,7 @@ instance DecodeNode Node where , name = name , entries = [] , children = Nothing - , format = Nothing + , ext = def } -- | Decode an argument in the node. @@ -849,7 +850,7 @@ children decoder = } pure b where - emptyNodeList = NodeList{nodes = [], format = Nothing} + emptyNodeList = NodeList{nodes = [], ext = def} {----- Decoding ValueData -----} diff --git a/src/KDL/Parser.hs b/src/KDL/Parser.hs index b830e0d..cfed145 100644 --- a/src/KDL/Parser.hs +++ b/src/KDL/Parser.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-| @@ -6,23 +7,38 @@ Implement the v2 parser specified at: https://kdl.dev/spec/#name-full-grammar module KDL.Parser ( parse, parseFile, + + -- * Configurable parsing + ParseConfig (..), + parseWith, + parseFileWith, ) where import Data.Bifunctor (first) +import Data.Default (def) import Data.Text (Text) import Data.Text qualified as Text import Data.Text.IO qualified as Text -import KDL.Parser.Internal (p_document) +import KDL.Parser.Internal ( + ParseConfig (..), + p_document, + runParser, + ) import KDL.Types (Document) import Text.Megaparsec qualified as Megaparsec parse :: Text -> Either Text Document -parse = parse' "" +parse = parseWith def + +parseFile :: FilePath -> IO (Either Text Document) +parseFile = parseFileWith def -parse' :: FilePath -> Text -> Either Text Document -parse' fp input = +parseWith :: ParseConfig -> Text -> Either Text Document +parseWith config input = first (Text.strip . Text.pack . Megaparsec.errorBundlePretty) $ - Megaparsec.parse p_document fp input + runParser config p_document input -parseFile :: FilePath -> IO (Either Text Document) -parseFile fp = parse' fp <$> Text.readFile fp +parseFileWith :: ParseConfig -> FilePath -> IO (Either Text Document) +parseFileWith config0 fp = parseWith config <$> Text.readFile fp + where + config = config0{filepath = fp} diff --git a/src/KDL/Parser/Internal.hs b/src/KDL/Parser/Internal.hs index 8648861..8a093fe 100644 --- a/src/KDL/Parser/Internal.hs +++ b/src/KDL/Parser/Internal.hs @@ -11,6 +11,8 @@ Implement the v2 parser specified at: https://kdl.dev/spec/#name-full-grammar -} module KDL.Parser.Internal ( Parser, + ParseConfig (..), + runParser, -- * (1) Compatibility p_bom, @@ -112,6 +114,8 @@ module KDL.Parser.Internal ( ) where import Control.Monad (guard, void, (>=>)) +import Control.Monad.Trans.Class qualified as Trans +import Control.Monad.Trans.Reader qualified as Reader import Control.Monad.Trans.State.Strict (StateT, evalStateT) import Control.Monad.Trans.State.Strict qualified as State import Data.Bifunctor (bimap) @@ -125,9 +129,11 @@ import Data.Char ( isSpace, ord, ) +import Data.Default (Default (..)) import Data.Either (isRight) import Data.Foldable (foldlM, traverse_) import Data.Foldable qualified as Seq (toList) +import Data.List.NonEmpty qualified as NonEmpty import Data.Maybe (catMaybes, fromMaybe, isJust) import Data.Scientific (Scientific) import Data.Scientific qualified as Scientific @@ -139,32 +145,60 @@ import Data.Text qualified as Text import Data.Void (Void) import KDL.Types ( Ann (..), + AnnExtension (..), AnnFormat (..), Document, Entry (..), + EntryExtension (..), EntryFormat (..), Identifier (..), + IdentifierExtension (..), IdentifierFormat (..), Node (..), + NodeExtension (..), NodeFormat (..), NodeList (..), + NodeListExtension (..), NodeListFormat (..), + Span (..), Value (..), ValueData (..), + ValueExtension (..), ValueFormat (..), ) +import KDL.Types qualified as AnnExtension (AnnExtension (..)) import KDL.Types qualified as AnnFormat (AnnFormat (..)) +import KDL.Types qualified as EntryExtension (EntryExtension (..)) import KDL.Types qualified as EntryFormat (EntryFormat (..)) +import KDL.Types qualified as Node (Node (..)) +import KDL.Types qualified as NodeExtension (NodeExtension (..)) import KDL.Types qualified as NodeFormat (NodeFormat (..)) +import KDL.Types qualified as NodeListExtension (NodeListExtension (..)) import KDL.Types qualified as NodeListFormat (NodeListFormat (..)) -import Text.Megaparsec +import Text.Megaparsec hiding (runParser) import Text.Megaparsec.Char +import Prelude hiding (span) #if !MIN_VERSION_base(4,20,0) import Data.Foldable (foldl') #endif -type Parser = Parsec Void Text +data ParseConfig = ParseConfig + { filepath :: FilePath + , includeSpans :: Bool + } + +instance Default ParseConfig where + def = + ParseConfig + { filepath = "" + , includeSpans = False + } + +type Parser = ParsecT Void Text (Reader.Reader ParseConfig) + +runParser :: ParseConfig -> Parser a -> Text -> Either (ParseErrorBundle Text Void) a +runParser config p = (`Reader.runReader` config) . runParserT p config.filepath {----- (1) Compatibility -----} @@ -208,7 +242,7 @@ p_nodes = label "nodes" $ do -- The grammar is left-associative, but we do right-associative to get -- correct backtracking semantics + good parse errors initialWS <- withSource_ $ many p_line_space - results <- fmap concat . manyWhile $ do + (results, span) <- withSpan . fmap concat . manyWhile $ do (sdash, parseNode) <- p_node (result, continue) <- resolveSlashdash sdash parseNode >>= \case @@ -224,7 +258,12 @@ p_nodes = label "nodes" $ do if null nodes then (leftoverWS, "") else ("", leftoverWS) - pure NodeList{format = Just NodeListFormat{..}, ..} + ext = + NodeListExtension + { format = Just NodeListFormat{..} + , span + } + pure NodeList{..} where manyWhile :: Parser (a, Bool) -> Parser [a] manyWhile p = @@ -263,9 +302,22 @@ p_node :: Parser (SlashdashResult, Parser (Node, Bool)) p_node = label "node" $ do (sdash, parseNode) <- p_base_node pure . (sdash,) $ do + spanStart <- startSpan node <- parseNode mTerminator <- optional p_node_terminator - pure (mapFormat (maybe id setTerminator mTerminator) node, isJust mTerminator) + span <- finishSpan spanStart + let node' = + case mTerminator of + Just terminator -> + node + { Node.ext = + node.ext + { NodeExtension.format = setTerminator terminator <$> node.ext.format + , NodeExtension.span = span + } + } + Nothing -> node + pure (node', isJust mTerminator) where setTerminator terminator format = format @@ -286,6 +338,8 @@ p_base_node :: Parser (SlashdashResult, Parser Node) p_base_node = label "base node" $ do sdash <- option NoSlashdash p_slashdash pure . (sdash,) $ do + spanStart <- startSpan + -- node ann initialAnn <- optional p_type postAnnWS <- withSource_ $ many p_node_space @@ -314,6 +368,8 @@ p_base_node = label "base node" $ do <*> p_node_children let beforeChildren = postEntriesWS <> slashdashedChildren1 <> fromMaybe "" preChildrenWS + span <- finishSpan spanStart + -- slashdashed node-children #2 slashdashedChildren2 <- p_slashdashed_children @@ -325,7 +381,13 @@ p_base_node = label "base node" $ do let beforeTerminator = "" terminator = "" - pure Node{format = Just NodeFormat{..}, ..} + let ext = + NodeExtension + { format = Just NodeFormat{..} + , span + } + + pure Node{..} where -- (node-space* (node-space | slashdash) node-prop-or-arg)* p_entries = fmap concat . many . label "node prop or arg" $ do @@ -387,11 +449,13 @@ p_escline = label "escline" $ do -- prop := string node-space* '=' node-space* value p_prop :: Parser Entry p_prop = label "property" $ do + spanStart <- startSpan name <- p_string'Identifier afterKey <- withSource_ $ many p_node_space _ <- char '=' afterEq <- withSource_ $ many p_node_space (value, leading) <- p_value + span <- finishSpan spanStart let format = EntryFormat { leading @@ -399,14 +463,15 @@ p_prop = label "property" $ do , afterEq , trailing = "" } - pure Entry{name = Just name, value, format = Just format} + ext = EntryExtension{format = Just format, span} + pure Entry{name = Just name, ..} {----- (3.5) Argument -----} -- | ref: (3.5) p_value'Entry :: Parser Entry p_value'Entry = label "argument" $ do - (value, leading) <- p_value + ((value, leading), span) <- withSpan p_value let format = EntryFormat { leading @@ -414,7 +479,12 @@ p_value'Entry = label "argument" $ do , afterEq = "" , trailing = "" } - pure Entry{name = Nothing, value, format = Just format} + ext = + EntryExtension + { format = Just format + , span + } + pure Entry{name = Nothing, ..} {----- (3.6) Children Block -----} @@ -431,6 +501,7 @@ p_node_children = label "children block" $ do -- value := type? node-space* (string | number | keyword) p_value :: Parser (Value, Text) p_value = label "value" $ do + spanStart <- startSpan initialAnn <- optional p_type postAnnWS <- withSource_ $ many p_node_space let (ann, leading) = @@ -445,8 +516,14 @@ p_value = label "value" $ do , p_keyword ] let repr = Just repr_ + span <- finishSpan spanStart - pure (Value{ann, data_, format = Just ValueFormat{..}}, leading) + let ext = + ValueExtension + { format = Just ValueFormat{..} + , span + } + pure (Value{..}, leading) -- | ref: (3.7) p_keyword :: Parser ValueData @@ -462,21 +539,26 @@ p_keyword = label "value keyword" $ do -- type := '(' node-space* string node-space* ')' p_type :: Parser Ann p_type = label "type annotation" $ do - between (char '(') (char ')') $ do - beforeId <- withSource_ $ many p_node_space - identifier <- p_string'Identifier - afterId <- withSource_ $ many p_node_space - let format = Just AnnFormat{leading = "", trailing = "", ..} - pure Ann{..} + ((identifier, format), span) <- + withSpan . between (char '(') (char ')') $ do + beforeId <- withSource_ $ many p_node_space + identifier <- p_string'Identifier + afterId <- withSource_ $ many p_node_space + let format = Just AnnFormat{leading = "", trailing = "", ..} + pure (identifier, format) + + let ext = AnnExtension{format, span} + pure Ann{identifier, ext} {----- (3.9) String -----} -- | ref: (3.9) p_string'Identifier :: Parser Identifier p_string'Identifier = do - (value, repr_) <- withSource p_string + ((value, repr_), span) <- withSpan . withSource $ p_string let repr = Just repr_ - pure Identifier{value, format = Just IdentifierFormat{repr}} + let ext = IdentifierExtension{format = Just IdentifierFormat{..}, span} + pure Identifier{..} -- | ref: (3.9) -- string := identifier-string | quoted-string | raw-string ΒΆ @@ -501,7 +583,7 @@ p_identifier_string = label "unquoted string" $ do ] isValidUnquotedString :: Text -> Bool -isValidUnquotedString = isRight . parse (p_identifier_string <* eof) "" +isValidUnquotedString = isRight . runParser def (p_identifier_string <* eof) -- | ref: (3.10) -- unambiguous-ident := @@ -1120,6 +1202,62 @@ withSource p = do withSource_ :: Parser a -> Parser Text withSource_ = fmap snd . withSource +newtype SpanStart = SpanStart (Maybe (State Text Void, SourcePos)) + +withSpan :: Parser a -> Parser (a, Span) +withSpan p = do + spanStart <- startSpan + a <- p + span <- finishSpan spanStart + pure (a, span) + +startSpan :: Parser SpanStart +startSpan = do + config <- Trans.lift Reader.ask + if not config.includeSpans + then pure . SpanStart $ Nothing + else do + start <- getSourcePos + startState <- getParserState + pure . SpanStart . Just $ (startState, start) + +finishSpan :: SpanStart -> Parser Span +finishSpan (SpanStart mStart) = + case mStart of + Nothing -> pure def + Just (startState, start) -> do + end <- getSourcePos + endState <- getParserState + let startLine = unPos start.sourceLine + startCol = unPos start.sourceColumn + (endLine, endCol) = + getEnd + startState + endState + startCol + (unPos end.sourceLine, unPos end.sourceColumn) + pure Span{..} + where + -- end.sourceColumn is off by 1, since the parser has already incremented + -- past the parsed element. So we need to simply subtract 1. However, if + -- end.sourceColumn == 1, it's at the start of a new line, and we need to + -- calculate the last column of the previous line. + getEnd startState endState startCol (endLine, endCol) = + if endCol > 1 + then (endLine, endCol - 1) + else + let len = endState.stateOffset - startState.stateOffset + source = Text.take len startState.stateInput + endCol' = case NonEmpty.nonEmpty $ Text.lines source of + -- source was empty, i.e. len == 0, so endCol := startCol + Nothing -> startCol + -- there were no newlines other than the last newline, so we're + -- offset from startCol + Just (line NonEmpty.:| []) -> startCol + Text.length line - 1 + -- source had multiple newlines, endCol is simply the length of the last line + Just sourceLines -> Text.length $ NonEmpty.last sourceLines + in (endLine - 1, endCol') + repeat0 :: (Monoid a) => Parser a -> Parser a repeat0 = fmap mconcat . many @@ -1161,16 +1299,16 @@ class HasFormat a where mapFormat :: (KdlFormat a -> KdlFormat a) -> a -> a instance HasFormat NodeList where type KdlFormat NodeList = NodeListFormat - mapFormat f NodeList{..} = NodeList{format = f <$> format, ..} + mapFormat f NodeList{..} = NodeList{ext = ext{NodeListExtension.format = f <$> ext.format}, ..} instance HasFormat Node where type KdlFormat Node = NodeFormat - mapFormat f Node{..} = Node{format = f <$> format, ..} + mapFormat f Node{..} = Node{ext = ext{NodeExtension.format = f <$> ext.format}, ..} instance HasFormat Ann where type KdlFormat Ann = AnnFormat - mapFormat f Ann{..} = Ann{format = f <$> format, ..} + mapFormat f Ann{..} = Ann{ext = ext{AnnExtension.format = f <$> ext.format}, ..} instance HasFormat Entry where type KdlFormat Entry = EntryFormat - mapFormat f Entry{..} = Entry{format = f <$> format, ..} + mapFormat f Entry{..} = Entry{ext = ext{EntryExtension.format = f <$> ext.format}, ..} class (HasFormat a) => HasWsFormat a where mapLeading :: (Text -> Text) -> a -> a diff --git a/src/KDL/Render.hs b/src/KDL/Render.hs index ffd0ef7..fa6048e 100644 --- a/src/KDL/Render.hs +++ b/src/KDL/Render.hs @@ -18,18 +18,24 @@ import Data.Text (Text) import Data.Text qualified as Text import KDL.Types ( Ann (..), + AnnExtension (..), AnnFormat (..), Document, Entry (..), + EntryExtension (..), EntryFormat (..), Identifier (..), + IdentifierExtension (..), IdentifierFormat (..), Node (..), + NodeExtension (..), NodeFormat (..), NodeList (..), + NodeListExtension (..), NodeListFormat (..), Value (..), ValueData (..), + ValueExtension (..), ValueFormat (..), ) @@ -41,31 +47,31 @@ type IndentLevel = Int renderNodeList :: IndentLevel -> NodeList -> Text renderNodeList lvl NodeList{..} = Text.concat - [ maybe (if lvl > 0 then "\n" else "") (.leading) format + [ maybe (if lvl > 0 then "\n" else "") (.leading) ext.format , foldMap (renderNode lvl) nodes - , maybe (indent (lvl - 1)) (.trailing) format + , maybe (indent (lvl - 1)) (.trailing) ext.format ] renderNode :: IndentLevel -> Node -> Text renderNode lvl Node{..} = Text.concat - [ maybe (indent lvl) (.leading) format + [ maybe (indent lvl) (.leading) ext.format , maybe "" renderAnn ann , renderIdentifier name , foldMap renderEntry entries , let def_ = if children == Nothing then "" else " " - in maybe def_ (.beforeChildren) format + in maybe def_ (.beforeChildren) ext.format , case children of Nothing -> "" Just nodes -> renderChildren lvl nodes - , maybe "" (.beforeTerminator) format - , maybe "\n" (.terminator) format - , maybe "" (.trailing) format + , maybe "" (.beforeTerminator) ext.format + , maybe "\n" (.terminator) ext.format + , maybe "" (.trailing) ext.format ] renderChildren :: IndentLevel -> NodeList -> Text renderChildren lvl nodeList = - case nodeList.format of + case nodeList.ext.format of -- Special case empty node list to render as "{}" Nothing | null nodeList.nodes -> "{}" _ -> "{" <> renderNodeList (lvl + 1) nodeList <> "}" @@ -76,37 +82,37 @@ indent lvl = Text.replicate lvl " " renderEntry :: Entry -> Text renderEntry Entry{..} = Text.concat - [ maybe " " (.leading) format + [ maybe " " (.leading) ext.format , case name of Nothing -> renderValue value Just nameId -> Text.concat [ renderIdentifier nameId - , maybe "" (.afterKey) format + , maybe "" (.afterKey) ext.format , "=" - , maybe "" (.afterEq) format + , maybe "" (.afterEq) ext.format , renderValue value ] - , maybe "" (.trailing) format + , maybe "" (.trailing) ext.format ] renderAnn :: Ann -> Text renderAnn Ann{..} = Text.concat - [ maybe "" (.leading) format + [ maybe "" (.leading) ext.format , "(" - , maybe "" (.beforeId) format + , maybe "" (.beforeId) ext.format , renderIdentifier identifier - , maybe "" (.afterId) format + , maybe "" (.afterId) ext.format , ")" - , maybe "" (.trailing) format + , maybe "" (.trailing) ext.format ] renderValue :: Value -> Text renderValue Value{..} = Text.concat [ maybe "" renderAnn ann - , fromMaybe (renderValueData data_) (format >>= (.repr)) + , fromMaybe (renderValueData data_) (ext.format >>= (.repr)) ] renderValueData :: ValueData -> Text @@ -191,4 +197,4 @@ renderValueData = \case c -> Text.singleton c renderIdentifier :: Identifier -> Text -renderIdentifier ident = fromMaybe ident.value (ident.format >>= (.repr)) +renderIdentifier ident = fromMaybe ident.value (ident.ext.format >>= (.repr)) diff --git a/src/KDL/Types.hs b/src/KDL/Types.hs index 992e0dc..6e7d1af 100644 --- a/src/KDL/Types.hs +++ b/src/KDL/Types.hs @@ -19,6 +19,7 @@ module KDL.Types ( -- * NodeList NodeList (..), + NodeListExtension (..), NodeListFormat (..), fromNodeList, nodeListFormat, @@ -33,6 +34,7 @@ module KDL.Types ( -- * Node Node (..), + NodeExtension (..), NodeFormat (..), nodeAnn, nodeName, @@ -48,6 +50,7 @@ module KDL.Types ( -- * Entry Entry (..), + EntryExtension (..), EntryFormat (..), entryName, entryValue, @@ -55,6 +58,7 @@ module KDL.Types ( -- * Value Value (..), + ValueExtension (..), ValueFormat (..), valueAnn, valueData, @@ -63,17 +67,22 @@ module KDL.Types ( -- * Ann Ann (..), + AnnExtension (..), AnnFormat (..), annIdentifier, annFormat, -- * Identifier Identifier (..), + IdentifierExtension (..), IdentifierFormat (..), fromIdentifier, identifierFormat, toIdentifier, + -- * Span + Span (..), + -- * Re-exports def, ) where @@ -97,7 +106,13 @@ docNodes = fromNodeList data NodeList = NodeList { nodes :: [Node] - , format :: Maybe NodeListFormat + , ext :: NodeListExtension + } + deriving (Show, Eq) + +data NodeListExtension = NodeListExtension + { format :: Maybe NodeListFormat + , span :: Span } deriving (Show, Eq) @@ -109,6 +124,12 @@ data NodeListFormat = NodeListFormat } deriving (Show, Eq) +instance Default NodeListExtension where + def = + NodeListExtension + { format = def + , span = def + } instance Default NodeListFormat where def = NodeListFormat @@ -120,7 +141,7 @@ fromNodeList :: NodeList -> [Node] fromNodeList = (.nodes) nodeListFormat :: NodeList -> Maybe NodeListFormat -nodeListFormat = (.format) +nodeListFormat = (.ext.format) -- | A helper to get all nodes with the given name filterNodes :: Text -> NodeList -> [Node] @@ -208,7 +229,13 @@ getDashNodesAt name = maybe [] (filterNodes "-") . (nodeChildren <=< lookupNode data Ann = Ann { identifier :: Identifier - , format :: Maybe AnnFormat + , ext :: AnnExtension + } + deriving (Show, Eq) + +data AnnExtension = AnnExtension + { format :: Maybe AnnFormat + , span :: Span } deriving (Show, Eq) @@ -224,6 +251,12 @@ data AnnFormat = AnnFormat } deriving (Show, Eq) +instance Default AnnExtension where + def = + AnnExtension + { format = def + , span = def + } instance Default AnnFormat where def = AnnFormat @@ -237,7 +270,7 @@ annIdentifier :: Ann -> Identifier annIdentifier = (.identifier) annFormat :: Ann -> Maybe AnnFormat -annFormat = (.format) +annFormat = (.ext.format) {----- Node -----} @@ -246,7 +279,13 @@ data Node = Node , name :: Identifier , entries :: [Entry] , children :: Maybe NodeList - , format :: Maybe NodeFormat + , ext :: NodeExtension + } + deriving (Show, Eq) + +data NodeExtension = NodeExtension + { format :: Maybe NodeFormat + , span :: Span } deriving (Show, Eq) @@ -264,6 +303,12 @@ data NodeFormat = NodeFormat } deriving (Show, Eq) +instance Default NodeExtension where + def = + NodeExtension + { format = def + , span = def + } instance Default NodeFormat where def = NodeFormat @@ -287,7 +332,7 @@ nodeChildren :: Node -> Maybe NodeList nodeChildren = (.children) nodeFormat :: Node -> Maybe NodeFormat -nodeFormat = (.format) +nodeFormat = (.ext.format) -- | Get all the positional arguments of the node. getArgs :: Node -> [Value] @@ -318,7 +363,13 @@ data Entry = Entry { name :: Maybe Identifier -- ^ The name of the entry, if it's a property, Nothing if it's a positional arg , value :: Value - , format :: Maybe EntryFormat + , ext :: EntryExtension + } + deriving (Show, Eq) + +data EntryExtension = EntryExtension + { format :: Maybe EntryFormat + , span :: Span } deriving (Show, Eq) @@ -334,6 +385,12 @@ data EntryFormat = EntryFormat } deriving (Show, Eq) +instance Default EntryExtension where + def = + EntryExtension + { format = def + , span = def + } instance Default EntryFormat where def = EntryFormat @@ -350,14 +407,20 @@ entryValue :: Entry -> Value entryValue = (.value) entryFormat :: Entry -> Maybe EntryFormat -entryFormat = (.format) +entryFormat = (.ext.format) {----- Value -----} data Value = Value { ann :: Maybe Ann , data_ :: ValueData - , format :: Maybe ValueFormat + , ext :: ValueExtension + } + deriving (Show, Eq) + +data ValueExtension = ValueExtension + { format :: Maybe ValueFormat + , span :: Span } deriving (Show, Eq) @@ -367,6 +430,12 @@ data ValueFormat = ValueFormat } deriving (Show, Eq) +instance Default ValueExtension where + def = + ValueExtension + { format = def + , span = def + } instance Default ValueFormat where def = ValueFormat @@ -380,7 +449,7 @@ valueData :: Value -> ValueData valueData = (.data_) valueFormat :: Value -> Maybe ValueFormat -valueFormat = (.format) +valueFormat = (.ext.format) data ValueData = String Text @@ -396,7 +465,13 @@ data ValueData data Identifier = Identifier { value :: Text - , format :: Maybe IdentifierFormat + , ext :: IdentifierExtension + } + deriving (Show, Eq, Ord) + +data IdentifierExtension = IdentifierExtension + { format :: Maybe IdentifierFormat + , span :: Span } deriving (Show, Eq, Ord) @@ -405,6 +480,12 @@ data IdentifierFormat = IdentifierFormat } deriving (Show, Eq, Ord) +instance Default IdentifierExtension where + def = + IdentifierExtension + { format = def + , span = def + } instance Default IdentifierFormat where def = IdentifierFormat @@ -415,7 +496,28 @@ fromIdentifier :: Identifier -> Text fromIdentifier = (.value) identifierFormat :: Identifier -> Maybe IdentifierFormat -identifierFormat = (.format) +identifierFormat = (.ext.format) toIdentifier :: Text -> Identifier -toIdentifier value = Identifier{value = value, format = Nothing} +toIdentifier value = Identifier{value = value, ext = def} + +{----- Span -----} + +-- | The span of a KDL element, if parsed with 'includeSpans'. If 'includeSpans' +-- was not enabled, all fields are set to 0. +data Span = Span + { startLine :: Int + , startCol :: Int + , endLine :: Int + , endCol :: Int + } + deriving (Show, Eq, Ord) + +instance Default Span where + def = + Span + { startLine = 0 + , startCol = 0 + , endLine = 0 + , endCol = 0 + } diff --git a/test/KDL/Decoder/ArrowSpec.hs b/test/KDL/Decoder/ArrowSpec.hs index 1022753..99d2756 100644 --- a/test/KDL/Decoder/ArrowSpec.hs +++ b/test/KDL/Decoder/ArrowSpec.hs @@ -43,16 +43,16 @@ apiSpec = do expected = Node { ann = Nothing - , name = Identifier{value = "foo", format = Nothing} + , name = Identifier{value = "foo", ext = KDL.def} , entries = [ Entry { name = Nothing - , value = Value{ann = Nothing, data_ = Number 1.0, format = Nothing} - , format = Nothing + , value = Value{ann = Nothing, data_ = Number 1.0, ext = KDL.def} + , ext = KDL.def } ] , children = Nothing - , format = Nothing + , ext = KDL.def } KDL.decodeWith decoder config `shouldBe` Right expected @@ -64,10 +64,10 @@ apiSpec = do fooNode = Node { ann = Nothing - , name = Identifier{value = "foo", format = Nothing} + , name = Identifier{value = "foo", ext = KDL.def} , entries = [] , children = Nothing - , format = Nothing + , ext = KDL.def } KDL.decodeWith decoder config `shouldBe` Right expected @@ -81,10 +81,10 @@ apiSpec = do node name = Node { ann = Nothing - , name = Identifier{value = name, format = Nothing} + , name = Identifier{value = name, ext = KDL.def} , entries = [] , children = Nothing - , format = Nothing + , ext = KDL.def } KDL.decodeWith decoder config `shouldBe` Right expected @@ -174,24 +174,24 @@ apiSpec = do fooNode2 = Node { ann = Nothing - , name = Identifier{value = "foo", format = Nothing} + , name = Identifier{value = "foo", ext = KDL.def} , entries = [ Entry { name = Nothing - , value = Value{ann = Nothing, data_ = Number 2.0, format = Nothing} - , format = Nothing + , value = Value{ann = Nothing, data_ = Number 2.0, ext = KDL.def} + , ext = KDL.def } ] , children = Nothing - , format = Nothing + , ext = KDL.def } barNode = Node { ann = Nothing - , name = Identifier{value = "bar", format = Nothing} + , name = Identifier{value = "bar", ext = KDL.def} , entries = [] , children = Nothing - , format = Nothing + , ext = KDL.def } KDL.decodeWith decoder config `shouldBe` Right expected @@ -519,13 +519,13 @@ apiSpec = do node name children = Node { ann = Nothing - , name = Identifier{value = name, format = Nothing} + , name = Identifier{value = name, ext = KDL.def} , entries = [] , children = if null children then Nothing - else Just NodeList{nodes = children, format = Nothing} - , format = Nothing + else Just NodeList{nodes = children, ext = KDL.def} + , ext = KDL.def } KDL.decodeWith decoder config `shouldBe` Right expected @@ -850,16 +850,16 @@ apiSpec = do expected = Node { ann = Nothing - , name = Identifier{value = "bar", format = Nothing} + , name = Identifier{value = "bar", ext = KDL.def} , entries = [ Entry { name = Nothing - , value = Value{ann = Nothing, data_ = String "test", format = Nothing} - , format = Nothing + , value = Value{ann = Nothing, data_ = String "test", ext = KDL.def} + , ext = KDL.def } ] , children = Nothing - , format = Nothing + , ext = KDL.def } decodeNode "foo" decoder config `shouldBe` Right expected @@ -891,7 +891,7 @@ apiSpec = do Value { ann = Nothing , data_ = data_ - , format = Nothing + , ext = KDL.def } KDL.decodeWith decoder config `shouldBe` Right [val $ Number 1, val $ String "asdf", val $ Bool True] diff --git a/test/KDL/Decoder/MonadSpec.hs b/test/KDL/Decoder/MonadSpec.hs index ff9401f..0780d36 100644 --- a/test/KDL/Decoder/MonadSpec.hs +++ b/test/KDL/Decoder/MonadSpec.hs @@ -37,16 +37,16 @@ apiSpec = do expected = Node { ann = Nothing - , name = Identifier{value = "foo", format = Nothing} + , name = Identifier{value = "foo", ext = KDL.def} , entries = [ Entry { name = Nothing - , value = Value{ann = Nothing, data_ = Number 1.0, format = Nothing} - , format = Nothing + , value = Value{ann = Nothing, data_ = Number 1.0, ext = KDL.def} + , ext = KDL.def } ] , children = Nothing - , format = Nothing + , ext = KDL.def } KDL.decodeWith decoder config `shouldBe` Right expected @@ -58,10 +58,10 @@ apiSpec = do fooNode = Node { ann = Nothing - , name = Identifier{value = "foo", format = Nothing} + , name = Identifier{value = "foo", ext = KDL.def} , entries = [] , children = Nothing - , format = Nothing + , ext = KDL.def } KDL.decodeWith decoder config `shouldBe` Right expected @@ -75,10 +75,10 @@ apiSpec = do node name = Node { ann = Nothing - , name = Identifier{value = name, format = Nothing} + , name = Identifier{value = name, ext = KDL.def} , entries = [] , children = Nothing - , format = Nothing + , ext = KDL.def } KDL.decodeWith decoder config `shouldBe` Right expected @@ -168,24 +168,24 @@ apiSpec = do fooNode2 = Node { ann = Nothing - , name = Identifier{value = "foo", format = Nothing} + , name = Identifier{value = "foo", ext = KDL.def} , entries = [ Entry { name = Nothing - , value = Value{ann = Nothing, data_ = Number 2.0, format = Nothing} - , format = Nothing + , value = Value{ann = Nothing, data_ = Number 2.0, ext = KDL.def} + , ext = KDL.def } ] , children = Nothing - , format = Nothing + , ext = KDL.def } barNode = Node { ann = Nothing - , name = Identifier{value = "bar", format = Nothing} + , name = Identifier{value = "bar", ext = KDL.def} , entries = [] , children = Nothing - , format = Nothing + , ext = KDL.def } KDL.decodeWith decoder config `shouldBe` Right expected @@ -513,13 +513,13 @@ apiSpec = do node name children = Node { ann = Nothing - , name = Identifier{value = name, format = Nothing} + , name = Identifier{value = name, ext = KDL.def} , entries = [] , children = if null children then Nothing - else Just NodeList{nodes = children, format = Nothing} - , format = Nothing + else Just NodeList{nodes = children, ext = KDL.def} + , ext = KDL.def } KDL.decodeWith decoder config `shouldBe` Right expected @@ -844,16 +844,16 @@ apiSpec = do expected = Node { ann = Nothing - , name = Identifier{value = "bar", format = Nothing} + , name = Identifier{value = "bar", ext = KDL.def} , entries = [ Entry { name = Nothing - , value = Value{ann = Nothing, data_ = String "test", format = Nothing} - , format = Nothing + , value = Value{ann = Nothing, data_ = String "test", ext = KDL.def} + , ext = KDL.def } ] , children = Nothing - , format = Nothing + , ext = KDL.def } decodeNode "foo" decoder config `shouldBe` Right expected @@ -885,7 +885,7 @@ apiSpec = do Value { ann = Nothing , data_ = data_ - , format = Nothing + , ext = KDL.def } KDL.decodeWith decoder config `shouldBe` Right [val $ Number 1, val $ String "asdf", val $ Bool True] diff --git a/test/KDL/ParserSpec.hs b/test/KDL/ParserSpec.hs index 5f9031c..613fbec 100644 --- a/test/KDL/ParserSpec.hs +++ b/test/KDL/ParserSpec.hs @@ -28,6 +28,11 @@ spec = do -- TODO: add more test "Unquoted numeric prop name" "foo 123=123" + describe "parseWith" $ do + it "parses a KDL document with spans" $ do + KDL.parseWith KDL.def{KDL.includeSpans = True} "foo 1 2 {\n bar 3\n}" + `shouldSatisfy` P.right P.matchesSnapshot + -- Most behavior tested in `parse` tests describe "parseFile" $ do it "parses a KDL document from a filepath" $ do diff --git a/test/KDL/RenderSpec.hs b/test/KDL/RenderSpec.hs index b8ca0a7..3aa2ca2 100644 --- a/test/KDL/RenderSpec.hs +++ b/test/KDL/RenderSpec.hs @@ -11,11 +11,11 @@ spec :: Spec spec = do describe "render" $ do describe "default formatting" $ do - let ident s = KDL.Identifier{value = s, format = KDL.def} + let ident s = KDL.Identifier{value = s, ext = KDL.def} fooAnn = KDL.Ann { identifier = ident "Foo" - , format = KDL.def + , ext = KDL.def } doc = KDL.NodeList @@ -30,9 +30,9 @@ spec = do KDL.Value { ann = Just fooAnn , data_ = KDL.Number 123 - , format = KDL.def + , ext = KDL.def } - , format = KDL.def + , ext = KDL.def } , KDL.Entry { name = Just $ ident "a" @@ -40,9 +40,9 @@ spec = do KDL.Value { ann = Just fooAnn , data_ = KDL.Number 123 - , format = KDL.def + , ext = KDL.def } - , format = KDL.def + , ext = KDL.def } , KDL.Entry { name = Nothing @@ -50,9 +50,9 @@ spec = do KDL.Value { ann = Nothing , data_ = KDL.String "test" - , format = KDL.def + , ext = KDL.def } - , format = KDL.def + , ext = KDL.def } , KDL.Entry { name = Just $ ident "b" @@ -60,9 +60,9 @@ spec = do KDL.Value { ann = Nothing , data_ = KDL.String "test" - , format = KDL.def + , ext = KDL.def } - , format = KDL.def + , ext = KDL.def } ] , children = @@ -82,17 +82,17 @@ spec = do , name = ident "baz" , entries = [] , children = Nothing - , format = KDL.def + , ext = KDL.def } ] - , format = KDL.def + , ext = KDL.def } - , format = KDL.def + , ext = KDL.def } ] - , format = KDL.def + , ext = KDL.def } - , format = KDL.def + , ext = KDL.def } , KDL.Node { ann = Just fooAnn @@ -102,12 +102,12 @@ spec = do Just KDL.NodeList { nodes = [] - , format = KDL.def + , ext = KDL.def } - , format = KDL.def + , ext = KDL.def } ] - , format = KDL.def + , ext = KDL.def } it "renders correctly" $ do diff --git a/test/KDL/TestUtils/AST.hs b/test/KDL/TestUtils/AST.hs index c785ff2..53a9d7a 100644 --- a/test/KDL/TestUtils/AST.hs +++ b/test/KDL/TestUtils/AST.hs @@ -12,7 +12,7 @@ instance ScrubFormat NodeList where scrubFormat NodeList{..} = NodeList { nodes = map scrubFormat nodes - , format = Nothing + , ext = def } instance ScrubFormat Node where scrubFormat Node{..} = @@ -21,31 +21,31 @@ instance ScrubFormat Node where , name = scrubFormat name , entries = map scrubFormat entries , children = scrubFormat <$> children - , format = Nothing + , ext = def } instance ScrubFormat Entry where scrubFormat Entry{..} = Entry { name = scrubFormat <$> name , value = scrubFormat value - , format = Nothing + , ext = def } instance ScrubFormat Value where scrubFormat Value{..} = Value { ann = scrubFormat <$> ann , data_ = data_ - , format = Nothing + , ext = def } instance ScrubFormat Ann where scrubFormat Ann{..} = Ann { identifier = scrubFormat identifier - , format = Nothing + , ext = def } instance ScrubFormat Identifier where scrubFormat Identifier{..} = Identifier { value = value - , format = Nothing + , ext = def } diff --git a/test/KDL/__snapshots__/ParserSpec.snap.md b/test/KDL/__snapshots__/ParserSpec.snap.md index b1f9737..eae5d14 100644 --- a/test/KDL/__snapshots__/ParserSpec.snap.md +++ b/test/KDL/__snapshots__/ParserSpec.snap.md @@ -21,7 +21,12 @@ NodeList , name = Identifier { value = "foo" - , format = Just IdentifierFormat { repr = Just "foo" } + , ext = + IdentifierExtension + { format = Just IdentifierFormat { repr = Just "foo" } + , span = + Span { startLine = 0 , startCol = 0 , endLine = 0 , endCol = 0 } + } } , entries = [ Entry @@ -29,18 +34,33 @@ NodeList Just Identifier { value = "hello" - , format = Just IdentifierFormat { repr = Just "hello" } + , ext = + IdentifierExtension + { format = Just IdentifierFormat { repr = Just "hello" } + , span = + Span { startLine = 0 , startCol = 0 , endLine = 0 , endCol = 0 } + } } , value = Value { ann = Nothing , data_ = String "world" - , format = Just ValueFormat { repr = Just "world" } + , ext = + ValueExtension + { format = Just ValueFormat { repr = Just "world" } + , span = + Span { startLine = 0 , startCol = 0 , endLine = 0 , endCol = 0 } + } + } + , ext = + EntryExtension + { format = + Just + EntryFormat + { leading = " " , afterKey = "" , afterEq = "" , trailing = "" } + , span = + Span { startLine = 0 , startCol = 0 , endLine = 0 , endCol = 0 } } - , format = - Just - EntryFormat - { leading = " " , afterKey = "" , afterEq = "" , trailing = "" } } , Entry { name = Nothing @@ -48,12 +68,22 @@ NodeList Value { ann = Nothing , data_ = Number 1.0 - , format = Just ValueFormat { repr = Just "1.0" } + , ext = + ValueExtension + { format = Just ValueFormat { repr = Just "1.0" } + , span = + Span { startLine = 0 , startCol = 0 , endLine = 0 , endCol = 0 } + } + } + , ext = + EntryExtension + { format = + Just + EntryFormat + { leading = " " , afterKey = "" , afterEq = "" , trailing = "" } + , span = + Span { startLine = 0 , startCol = 0 , endLine = 0 , endCol = 0 } } - , format = - Just - EntryFormat - { leading = " " , afterKey = "" , afterEq = "" , trailing = "" } } ] , children = @@ -65,34 +95,238 @@ NodeList , name = Identifier { value = "bar" - , format = Just IdentifierFormat { repr = Just "bar" } + , ext = + IdentifierExtension + { format = Just IdentifierFormat { repr = Just "bar" } + , span = + Span + { startLine = 0 + , startCol = 0 + , endLine = 0 + , endCol = 0 + } + } } , entries = [] , children = Nothing - , format = - Just - NodeFormat - { leading = " " - , beforeChildren = "" - , beforeTerminator = "" - , terminator = ";" - , trailing = "" - } + , ext = + NodeExtension + { format = + Just + NodeFormat + { leading = " " + , beforeChildren = "" + , beforeTerminator = "" + , terminator = ";" + , trailing = "" + } + , span = + Span { startLine = 0 , startCol = 0 , endLine = 0 , endCol = 0 } + } } ] - , format = Just NodeListFormat { leading = "" , trailing = " " } + , ext = + NodeListExtension + { format = Just NodeListFormat { leading = "" , trailing = " " } + , span = + Span { startLine = 0 , startCol = 0 , endLine = 0 , endCol = 0 } + } } - , format = + , ext = + NodeExtension + { format = + Just + NodeFormat + { leading = "" + , beforeChildren = " " + , beforeTerminator = "" + , terminator = "" + , trailing = "" + } + , span = + Span { startLine = 0 , startCol = 0 , endLine = 0 , endCol = 0 } + } + } + ] + , ext = + NodeListExtension + { format = Just NodeListFormat { leading = "" , trailing = "" } + , span = + Span { startLine = 0 , startCol = 0 , endLine = 0 , endCol = 0 } + } + } +``` + +## parseWith / parses a KDL document with spans + +```haskell +NodeList + { nodes = + [ Node + { ann = Nothing + , name = + Identifier + { value = "foo" + , ext = + IdentifierExtension + { format = Just IdentifierFormat { repr = Just "foo" } + , span = + Span { startLine = 1 , startCol = 1 , endLine = 1 , endCol = 3 } + } + } + , entries = + [ Entry + { name = Nothing + , value = + Value + { ann = Nothing + , data_ = Number 1.0 + , ext = + ValueExtension + { format = Just ValueFormat { repr = Just "1" } + , span = + Span { startLine = 1 , startCol = 5 , endLine = 1 , endCol = 5 } + } + } + , ext = + EntryExtension + { format = + Just + EntryFormat + { leading = " " , afterKey = "" , afterEq = "" , trailing = "" } + , span = + Span { startLine = 1 , startCol = 5 , endLine = 1 , endCol = 5 } + } + } + , Entry + { name = Nothing + , value = + Value + { ann = Nothing + , data_ = Number 2.0 + , ext = + ValueExtension + { format = Just ValueFormat { repr = Just "2" } + , span = + Span { startLine = 1 , startCol = 7 , endLine = 1 , endCol = 7 } + } + } + , ext = + EntryExtension + { format = + Just + EntryFormat + { leading = " " , afterKey = "" , afterEq = "" , trailing = "" } + , span = + Span { startLine = 1 , startCol = 7 , endLine = 1 , endCol = 7 } + } + } + ] + , children = Just - NodeFormat - { leading = "" - , beforeChildren = " " - , beforeTerminator = "" - , terminator = "" - , trailing = "" + NodeList + { nodes = + [ Node + { ann = Nothing + , name = + Identifier + { value = "bar" + , ext = + IdentifierExtension + { format = Just IdentifierFormat { repr = Just "bar" } + , span = + Span + { startLine = 2 + , startCol = 3 + , endLine = 2 + , endCol = 5 + } + } + } + , entries = + [ Entry + { name = Nothing + , value = + Value + { ann = Nothing + , data_ = Number 3.0 + , ext = + ValueExtension + { format = Just ValueFormat { repr = Just "3" } + , span = + Span + { startLine = 2 + , startCol = 7 + , endLine = 2 + , endCol = 7 + } + } + } + , ext = + EntryExtension + { format = + Just + EntryFormat + { leading = " " + , afterKey = "" + , afterEq = "" + , trailing = "" + } + , span = + Span + { startLine = 2 + , startCol = 7 + , endLine = 2 + , endCol = 7 + } + } + } + ] + , children = Nothing + , ext = + NodeExtension + { format = + Just + NodeFormat + { leading = "\n " + , beforeChildren = "" + , beforeTerminator = "" + , terminator = "\n" + , trailing = "" + } + , span = + Span { startLine = 2 , startCol = 3 , endLine = 2 , endCol = 7 } + } + } + ] + , ext = + NodeListExtension + { format = Just NodeListFormat { leading = "" , trailing = "" } + , span = + Span { startLine = 2 , startCol = 3 , endLine = 2 , endCol = 7 } + } } + , ext = + NodeExtension + { format = + Just + NodeFormat + { leading = "" + , beforeChildren = " " + , beforeTerminator = "" + , terminator = "" + , trailing = "" + } + , span = + Span { startLine = 1 , startCol = 1 , endLine = 3 , endCol = 1 } + } } ] - , format = Just NodeListFormat { leading = "" , trailing = "" } + , ext = + NodeListExtension + { format = Just NodeListFormat { leading = "" , trailing = "" } + , span = + Span { startLine = 1 , startCol = 1 , endLine = 3 , endCol = 1 } + } } ```