abnf 0.2.0.0 → 0.3.0.0
raw patch · 10 files changed
+225/−28 lines, 10 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Text.ABNF.ABNF.Types: type Identifier = Text
- Text.ABNF.Document.Types: instance GHC.Classes.Eq Text.ABNF.Document.Types.Content
- Text.ABNF.Document.Types: instance GHC.Classes.Eq Text.ABNF.Document.Types.Document
- Text.ABNF.Document.Types: instance GHC.Show.Show Text.ABNF.Document.Types.Content
- Text.ABNF.Document.Types: instance GHC.Show.Show Text.ABNF.Document.Types.Document
- Text.ABNF.Document.Types: type Identifier = Text
+ Text.ABNF.ABNF: Rule :: Text -> DefinedAs -> SumSpec -> Rule
+ Text.ABNF.ABNF: canonicalizeRules :: Text -> [Rule] -> Maybe Rule
+ Text.ABNF.ABNF: data Rule
+ Text.ABNF.ABNF: parseABNF :: String -> Text -> Either (ParseError Char Dec) [Rule]
+ Text.ABNF.ABNF: rulelist :: Parser [Rule]
+ Text.ABNF.ABNF.Parser: parseABNF :: String -> Text -> Either (ParseError Char Dec) [Rule]
+ Text.ABNF.Document: Document :: Text -> [Content a] -> Document a
+ Text.ABNF.Document: NonTerminal :: (Document a) -> Content a
+ Text.ABNF.Document: Terminal :: a -> Content a
+ Text.ABNF.Document: data Content a
+ Text.ABNF.Document: data Document a
+ Text.ABNF.Document: filterDocument :: (Document a -> Bool) -> Document a -> Maybe (Document a)
+ Text.ABNF.Document: generateParser :: Rule -> Parser (Document Text)
+ Text.ABNF.Document: parseDocument :: Rule -> Text -> Either String (Document Text)
+ Text.ABNF.Document: squashContent :: Monoid a => [Content a] -> a
+ Text.ABNF.Document: squashDocument :: Monoid a => Document a -> Document a
+ Text.ABNF.Document: squashDocumentOn :: Monoid a => (Document a -> Bool) -> Document a -> Document a
+ Text.ABNF.Document.Operations: filterDocument :: (Document a -> Bool) -> Document a -> Maybe (Document a)
+ Text.ABNF.Document.Operations: squashContent :: Monoid a => [Content a] -> a
+ Text.ABNF.Document.Operations: squashDocument :: Monoid a => Document a -> Document a
+ Text.ABNF.Document.Operations: squashDocumentOn :: Monoid a => (Document a -> Bool) -> Document a -> Document a
+ Text.ABNF.Document.Parser: parseDocument :: Rule -> Text -> Either String (Document Text)
+ Text.ABNF.Document.Types: instance GHC.Base.Applicative Text.ABNF.Document.Types.Content
+ Text.ABNF.Document.Types: instance GHC.Base.Functor Text.ABNF.Document.Types.Content
+ Text.ABNF.Document.Types: instance GHC.Base.Functor Text.ABNF.Document.Types.Document
+ Text.ABNF.Document.Types: instance GHC.Base.Monad Text.ABNF.Document.Types.Content
+ Text.ABNF.Document.Types: instance GHC.Classes.Eq a => GHC.Classes.Eq (Text.ABNF.Document.Types.Content a)
+ Text.ABNF.Document.Types: instance GHC.Classes.Eq a => GHC.Classes.Eq (Text.ABNF.Document.Types.Document a)
+ Text.ABNF.Document.Types: instance GHC.Show.Show a => GHC.Show.Show (Text.ABNF.Document.Types.Content a)
+ Text.ABNF.Document.Types: instance GHC.Show.Show a => GHC.Show.Show (Text.ABNF.Document.Types.Document a)
- Text.ABNF.ABNF.Canonicalizer: canonicalizeRules :: Identifier -> [Rule] -> Maybe Rule
+ Text.ABNF.ABNF.Canonicalizer: canonicalizeRules :: Text -> [Rule] -> Maybe Rule
- Text.ABNF.ABNF.Parser: identifier :: Parser Identifier
+ Text.ABNF.ABNF.Parser: identifier :: Parser Text
- Text.ABNF.ABNF.Types: Rule :: Identifier -> DefinedAs -> SumSpec -> Rule
+ Text.ABNF.ABNF.Types: Rule :: Text -> DefinedAs -> SumSpec -> Rule
- Text.ABNF.ABNF.Types: RuleElement' :: Identifier -> Element
+ Text.ABNF.ABNF.Types: RuleElement' :: Text -> Element
- Text.ABNF.Document.Parser: generateParser :: Rule -> Parser Document
+ Text.ABNF.Document.Parser: generateParser :: Rule -> Parser (Document Text)
- Text.ABNF.Document.Parser: parseElem :: Element -> Parser [Content]
+ Text.ABNF.Document.Parser: parseElem :: Element -> Parser [Content Text]
- Text.ABNF.Document.Parser: parseLiteral :: Literal -> Parser [Content]
+ Text.ABNF.Document.Parser: parseLiteral :: Literal -> Parser [Content Text]
- Text.ABNF.Document.Parser: parseProdSpec :: ProductSpec -> Parser [Content]
+ Text.ABNF.Document.Parser: parseProdSpec :: ProductSpec -> Parser [Content Text]
- Text.ABNF.Document.Parser: parseRepetition :: Repetition -> Parser [Content]
+ Text.ABNF.Document.Parser: parseRepetition :: Repetition -> Parser [Content Text]
- Text.ABNF.Document.Parser: parseRule :: Rule -> Parser Document
+ Text.ABNF.Document.Parser: parseRule :: Rule -> Parser (Document Text)
- Text.ABNF.Document.Parser: parseSumSpec :: SumSpec -> Parser [Content]
+ Text.ABNF.Document.Parser: parseSumSpec :: SumSpec -> Parser [Content Text]
- Text.ABNF.Document.Types: Document :: Identifier -> [Content] -> Document
+ Text.ABNF.Document.Types: Document :: Text -> [Content a] -> Document a
- Text.ABNF.Document.Types: NonTerminal :: Document -> Content
+ Text.ABNF.Document.Types: NonTerminal :: (Document a) -> Content a
- Text.ABNF.Document.Types: Terminal :: Text -> Content
+ Text.ABNF.Document.Types: Terminal :: a -> Content a
- Text.ABNF.Document.Types: data Content
+ Text.ABNF.Document.Types: data Content a
- Text.ABNF.Document.Types: data Document
+ Text.ABNF.Document.Types: data Document a
Files
- abnf.cabal +5/−2
- src/Text/ABNF.hs +17/−0
- src/Text/ABNF/ABNF.hs +27/−1
- src/Text/ABNF/ABNF/Canonicalizer.hs +7/−2
- src/Text/ABNF/ABNF/Parser.hs +11/−1
- src/Text/ABNF/ABNF/Types.hs +8/−4
- src/Text/ABNF/Document.hs +48/−1
- src/Text/ABNF/Document/Operations.hs +53/−0
- src/Text/ABNF/Document/Parser.hs +26/−12
- src/Text/ABNF/Document/Types.hs +23/−5
abnf.cabal view
@@ -2,7 +2,7 @@ -- see http://haskell.org/cabal/users-guide/ name: abnf-version: 0.2.0.0+version: 0.3.0.0 synopsis: Parse ABNF and generate parsers for the specified document description: You can use this library to parse an ABNF document and generate a parser from that ABNF to read a document@@ -15,6 +15,7 @@ homepage: https://github.com/Xandaros/abnf.git category: Text build-type: Simple+stability: alpha -- extra-source-files: cabal-version: >=1.10 @@ -23,13 +24,15 @@ location: https://github.com/Xandaros/abnf.git library- exposed-modules: Text.ABNF.ABNF,+ exposed-modules: Text.ABNF,+ Text.ABNF.ABNF, Text.ABNF.ABNF.Parser, Text.ABNF.ABNF.Types, Text.ABNF.ABNF.Canonicalizer, Text.ABNF.Document, Text.ABNF.Document.Parser, Text.ABNF.Document.Types,+ Text.ABNF.Document.Operations, Text.ABNF.PrettyPrinter ghc-options: -W -- other-modules:
+ src/Text/ABNF.hs view
@@ -0,0 +1,17 @@+{-|+Module : Text.ABNF+Description : Top-level module+Copyright : (c) Martin Zeller, 2016+License : BSD2+Maintainer : Martin Zeller <mz.bremerhaven@gmail.com>+Stability : experimental+Portability : non-portable+-}++module Text.ABNF+ ( module Text.ABNF.ABNF+ , module Text.ABNF.Document+ ) where++import Text.ABNF.ABNF+import Text.ABNF.Document
src/Text/ABNF/ABNF.hs view
@@ -1,1 +1,27 @@-module Text.ABNF.ABNF where+{-|+Module : Text.ABNF.ABNF+Description : ABNF+Copyright : (c) Martin Zeller, 2016+License : BSD2+Maintainer : Martin Zeller <mz.bremerhaven@gmail.com>+Stability : experimental+Portability : non-portable+-}++module Text.ABNF.ABNF+ (+ -- * ABNF types+ -- | Re-exported from "Text.ABNF.ABNF.Types"+ Rule(..)+ -- * Parsing ABNF Rules+ -- | Re-exported from "Text.ABNF.ABNF.Parser"+ , rulelist+ , parseABNF+ -- * Canonicalizing ABNF Rules+ -- | Re-exported from "Text.ABNF.ABNF.Canonicalizer"+ , canonicalizeRules+ ) where++import Text.ABNF.ABNF.Types (Rule(..))+import Text.ABNF.ABNF.Parser (rulelist, parseABNF)+import Text.ABNF.ABNF.Canonicalizer (canonicalizeRules)
src/Text/ABNF/ABNF/Canonicalizer.hs view
@@ -18,9 +18,14 @@ import Text.ABNF.ABNF.Types -type RuleMap = Map.Map Identifier Rule+type RuleMap = Map.Map Text.Text Rule -canonicalizeRules :: Identifier -> [Rule] -> Maybe Rule+-- | Canonicalize a list of 'Rule's, leaving only a single 'Rule'.+canonicalizeRules :: Text.Text -- ^ The main, or top-level, 'Rule'+ -> [Rule] -- ^ List of 'Rule's to canonicalize+ -> Maybe Rule -- ^ The operation may fail for a number of+ -- reasons, for example, because the main 'Rule'+ -- does not exist canonicalizeRules mainRuleIdent rules = do let (defs, adds) = partition isAdd rules ruleMap = foldr (\rule@(Rule ident _ _) curMap ->
src/Text/ABNF/ABNF/Parser.hs view
@@ -25,12 +25,22 @@ import Text.ABNF.ABNF.Types -identifier :: Parser Identifier+identifier :: Parser Text.Text identifier = Text.pack <$> do firstChar <- letterChar otherChars <- many $ alphaNumChar <|> char '-' pure (firstChar:otherChars) +-- | Convencience function to directly parse a list of rules.+-- This is equivalent to+--+-- @+-- 'parse' 'rulelist'+-- @+parseABNF :: String -> Text.Text -> Either (ParseError Char Dec) [Rule]+parseABNF = parse rulelist++-- | The main parser, which parses a list of 'Rule's. rulelist :: Parser [Rule] rulelist = catMaybes <$> (some $ Just <$> rule <|> (many wsp *> c_nl *> pure Nothing))
src/Text/ABNF/ABNF/Types.hs view
@@ -14,9 +14,13 @@ import qualified Data.Text as Text -type Identifier = Text.Text--data Rule = Rule Identifier DefinedAs SumSpec+-- | A 'Rule' represents a single entry in your ABNF. It could, for example,+-- look like this:+--+-- @+-- CRLF = %x0D.0A+-- @+data Rule = Rule Text.Text DefinedAs SumSpec deriving (Show, Eq) data SumSpec = SumSpec [ProductSpec]@@ -31,7 +35,7 @@ data Repeat = Repeat Int (Maybe Int) deriving (Show, Eq) -data Element = RuleElement' Identifier+data Element = RuleElement' Text.Text | RuleElement Rule | GroupElement Group | OptionElement Group
src/Text/ABNF/Document.hs view
@@ -1,1 +1,48 @@-module Text.ABNF.Document where+{-|+Module : Text.ABNF.Document+Description : Documents according to an ABNF definition+Copyright : (c) Martin Zeller, 2016+License : BSD2+Maintainer : Martin Zeller <mz.bremerhaven@gmail.com>+Stability : experimental+Portability : non-portable+-}++module Text.ABNF.Document+ (+ -- * Document types+ -- | Re-exported from "Text.ABNF.Document.Types"+ Document(..)+ , Content(..)+ -- * Reducing documents+ -- | Re-exported from "Text.ABNF.Document.Operations"+ --+ -- In most cases, you don't want to work with the full tree of a 'Document'.+ -- You can use these cases to 'filterDocument' away any branches you do not+ -- need and 'squashDocumentOn' those, where you don't need it as+ -- fine-grained.+ --+ -- This is incredibly useful if you have rules that parse single characters.+ , filterDocument+ , squashDocument+ , squashDocumentOn+ , squashContent+ -- * Parsing documents+ -- | Re-exported from "Text.ABNF.Document.Parser"+ , generateParser+ , parseDocument+ ) where++import Text.ABNF.Document.Types ( Document(..)+ , Content(..)+ )++import Text.ABNF.Document.Operations ( filterDocument+ , squashDocument+ , squashDocumentOn+ , squashContent+ )++import Text.ABNF.Document.Parser ( generateParser+ , parseDocument+ )
+ src/Text/ABNF/Document/Operations.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-|+Module : Text.ABNF.Document.Operations+Description : Some operations on documents+Copyright : (c) Martin Zeller, 2016+License : BSD2+Maintainer : Martin Zeller <mz.bremerhaven@gmail.com>+Stability : experimental+Portability : ScopedTypeVariables+-}++module Text.ABNF.Document.Operations where++import Data.Maybe (catMaybes)+import Data.Monoid ((<>))++import Text.ABNF.Document.Types++-- | Filter documents according to some predicate.+-- Similar to 'filter' in the Prelude.+filterDocument :: forall a. (Document a -> Bool) -- ^ Predicate to check+ -> Document a -- ^ Document to filter+ -> Maybe (Document a) -- ^ Returns 'Nothing' if the+ -- predicate fails, cascades+ -- otherwise+filterDocument pred doc@(Document ident conts) | pred doc = Just . Document ident $ (catMaybes . fmap filterNT $ conts)+ | otherwise = Nothing+ where+ filterNT :: Content a -> Maybe (Content a)+ filterNT a@(Terminal _) = Just a+ filterNT (NonTerminal doc) | pred doc = NonTerminal <$> filterDocument pred doc+ | otherwise = Nothing++-- | Squash all contents of a 'Document' into a single 'Terminal'+squashDocument :: Monoid a => Document a -> Document a+squashDocument (Document ident conts) = Document ident [Terminal $ squashContent conts]++-- | Squash all contents of a 'Document' which matches the predicate+-- See also 'squashDocument'+squashDocumentOn :: forall a. Monoid a => (Document a -> Bool) -> Document a -> Document a+squashDocumentOn pred doc@(Document ident conts) | pred doc = squashDocument doc+ | otherwise = Document ident (squashNT <$> conts)+ where+ squashNT :: Content a -> Content a+ squashNT (Terminal a) = Terminal a+ squashNT (NonTerminal doc) = NonTerminal $ squashDocumentOn pred doc++-- | Squash all contents using the 'Monoid' instance of @a@, cascading into+-- 'NonTerminal's.+squashContent :: Monoid a => [Content a] -> a+squashContent [] = mempty+squashContent ((Terminal a):xs) = a <> squashContent xs+squashContent ((NonTerminal (Document _ conts)):xs) = squashContent conts <> squashContent xs
src/Text/ABNF/Document/Parser.hs view
@@ -20,26 +20,40 @@ import Data.Foldable (asum) import Data.Monoid ((<>)) -import qualified Data.Text as Text+import qualified Data.Text as T import Data.Attoparsec.Text import Text.ABNF.ABNF.Types import Text.ABNF.Document.Types -generateParser :: Rule -> Parser Document+-- | Convenience function to directly parse a 'Document'+parseDocument :: Rule -- ^ 'Rule' to parse against+ -> T.Text -- ^ 'Text' to parse+ -> Either String (Document T.Text) -- ^ Return a 'String'+ -- describing the error or the+ -- successfully parsed+ -- 'Document'.+ --+ -- The format of the 'String'+ -- is as returned by+ -- <https://hackage.haskell.org/package/attoparsec attoparsec>.+parseDocument = parseOnly . generateParser++-- | Generate an <https://hackage.haskell.org/package/attoparsec attoparsec> parser+generateParser :: Rule -> Parser (Document T.Text) generateParser = parseRule -parseRule :: Rule -> Parser Document+parseRule :: Rule -> Parser (Document T.Text) parseRule (Rule ident _ spec) = Document ident <$> parseSumSpec spec <?> "Rule" -parseSumSpec :: SumSpec -> Parser [Content]+parseSumSpec :: SumSpec -> Parser [Content T.Text] parseSumSpec (SumSpec prodspecs) = asum (map parseProdSpec prodspecs) <?> "Sum" -parseProdSpec :: ProductSpec -> Parser [Content]+parseProdSpec :: ProductSpec -> Parser [Content T.Text] parseProdSpec (ProductSpec reps) = join <$> (sequence $ map parseRepetition reps) <?> "Product" -parseRepetition :: Repetition -> Parser [Content]+parseRepetition :: Repetition -> Parser [Content T.Text] -- any number of times parseRepetition (Repetition (Repeat 0 Nothing) elem) = join <$> (many $ parseElem elem)@@ -61,20 +75,20 @@ liftA2 (++) (parseElem elem) (parseRepetition (Repetition (Repeat (n-1) x) elem)) -parseElem :: Element -> Parser [Content]+parseElem :: Element -> Parser [Content T.Text] parseElem (RuleElement rule) = toList . NonTerminal <$> parseRule rule <?> "Rule element"-parseElem (RuleElement' ruleName) = fail . Text.unpack $ "Unknown rule: " <> ruleName+parseElem (RuleElement' ruleName) = fail . T.unpack $ "Unknown rule: " <> ruleName parseElem (GroupElement (Group spec)) = parseSumSpec spec <?> "Group element" parseElem (OptionElement (Group spec)) = parseSumSpec spec <|> pure [] <?> "Optional element" parseElem (LiteralElement lit) = parseLiteral lit <?> "Literal element" -parseLiteral :: Literal -> Parser [Content]+parseLiteral :: Literal -> Parser [Content T.Text] parseLiteral (CharLit lit) = toList . Terminal <$> asciiCI lit <?> "String literal" parseLiteral (NumLit lit) = toList . Terminal <$> parseNumLit lit -parseNumLit :: NumLit -> Parser Text.Text-parseNumLit (IntLit ints) = (Text.pack <$> (sequence (char . chr <$> ints)) <?> "Int-defined character")-parseNumLit (RangeLit x1 x2) = Text.pack . toList <$> (oneOf $ chr <$> [x1..x2]) <?> "Range literal"+parseNumLit :: NumLit -> Parser T.Text+parseNumLit (IntLit ints) = (T.pack <$> (sequence (char . chr <$> ints)) <?> "Int-defined character")+parseNumLit (RangeLit x1 x2) = T.pack . toList <$> (oneOf $ chr <$> [x1..x2]) <?> "Range literal" toList :: a -> [a] toList = pure
src/Text/ABNF/Document/Types.hs view
@@ -11,11 +11,29 @@ import qualified Data.Text as Text -type Identifier = Text.Text--data Document = Document Identifier [Content]+-- | A 'Document' represents a tree of the parsed input. Directly after parsing,+-- every 'Rule' that was applied, gets a node in the tree.+--+-- You can use 'Text.ABNF.Document.filterDocument' and+-- 'Text.ABNF.Document.squashDocumentOn' to reduce the tree into a more+-- managable shape.+data Document a = Document Text.Text [Content a] deriving (Show, Eq) -data Content = Terminal Text.Text- | NonTerminal Document+data Content a = Terminal a+ | NonTerminal (Document a) deriving (Show, Eq)++instance Functor Document where+ fmap f (Document ident conts) = Document ident $ fmap (fmap f) conts++instance Functor Content where+ fmap f (Terminal a) = Terminal $ f a+ fmap f (NonTerminal doc) = NonTerminal $ fmap f doc++instance Applicative Content++instance Monad Content where+ return = Terminal+ Terminal a >>= f = f a+ NonTerminal (Document ident cs) >>= f = NonTerminal (Document ident (map (>>= f) cs))