diff options
| -rw-r--r-- | bbcode/src/Text/BBCode.hs | 28 | ||||
| -rw-r--r-- | bbcode/src/Text/BBCode/Lexer.hs | 5 | ||||
| -rw-r--r-- | bbcode/thermoprint-bbcode.cabal | 1 | ||||
| -rw-r--r-- | bbcode/thermoprint-bbcode.nix | 6 |
4 files changed, 28 insertions, 12 deletions
diff --git a/bbcode/src/Text/BBCode.hs b/bbcode/src/Text/BBCode.hs index a6de7b4..3828c22 100644 --- a/bbcode/src/Text/BBCode.hs +++ b/bbcode/src/Text/BBCode.hs | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | module Text.BBCode | 4 | module Text.BBCode |
| 5 | ( TreeError(..) | 5 | ( TreeError(..) |
| 6 | , BBLabel | ||
| 6 | , rose | 7 | , rose |
| 7 | , matches | 8 | , matches |
| 8 | ) where | 9 | ) where |
| @@ -14,6 +15,7 @@ import Control.Exception (Exception) | |||
| 14 | import Data.Typeable (Typeable) | 15 | import Data.Typeable (Typeable) |
| 15 | 16 | ||
| 16 | import Control.Monad (unless) | 17 | import Control.Monad (unless) |
| 18 | import Data.Function (on) | ||
| 17 | 19 | ||
| 18 | import Text.BBCode.Lexer (BBToken(..), token) | 20 | import Text.BBCode.Lexer (BBToken(..), token) |
| 19 | 21 | ||
| @@ -21,32 +23,42 @@ import Data.Tree | |||
| 21 | import Data.Tree.Zipper (TreePos, Empty, Full) | 23 | import Data.Tree.Zipper (TreePos, Empty, Full) |
| 22 | import qualified Data.Tree.Zipper as Z | 24 | import qualified Data.Tree.Zipper as Z |
| 23 | 25 | ||
| 26 | import Data.Map (Map) | ||
| 27 | import qualified Data.Map as Map | ||
| 28 | |||
| 29 | import Data.CaseInsensitive (CI) | ||
| 30 | import qualified Data.CaseInsensitive as CI | ||
| 31 | |||
| 24 | data TreeError = ImbalancedTags Text Text | 32 | data TreeError = ImbalancedTags Text Text |
| 25 | | LeftoverClose Text | 33 | | LeftoverClose Text |
| 26 | deriving (Show, Eq, Generic, Typeable) | 34 | deriving (Show, Eq, Generic, Typeable) |
| 27 | 35 | ||
| 28 | instance Exception TreeError | 36 | instance Exception TreeError |
| 29 | 37 | ||
| 38 | type BBLabel = (Text, Map Text (Maybe Text)) | ||
| 39 | |||
| 30 | matches :: Text -> Text -> Bool | 40 | matches :: Text -> Text -> Bool |
| 31 | -- ^ @`matches` "open" "close"@ should be true iff @[/close]@ is a valid closing tag for @[open]@ | 41 | -- ^ @`matches` "open" "close"@ should be true iff @[/close]@ is a valid closing tag for @[open]@ |
| 32 | -- | 42 | -- |
| 33 | -- Until we allow for attributes this is equality according to `(==)` | 43 | -- > (==) `on` CI.mk |
| 34 | matches = (==) | 44 | matches = (==) `on` CI.mk |
| 35 | 45 | ||
| 36 | rose :: [BBToken] -> Either TreeError (Forest Text) | 46 | rose :: [BBToken] -> Either TreeError (Forest BBLabel) |
| 37 | -- ^ Assuming that both tags and content have the same type (we use 'Text') bbcode is a flat representation of a rose tree | 47 | -- ^ Assuming that both tags and content have the same type (we use 'Text') bbcode is a flat representation of a rose tree |
| 38 | rose = fmap Z.toForest . flip rose' (Z.fromForest []) | 48 | rose = fmap Z.toForest . flip rose' (Z.fromForest []) |
| 39 | where | 49 | where |
| 40 | rose' :: [BBToken] -> TreePos Empty Text -> Either TreeError (TreePos Empty Text) | 50 | rose' :: [BBToken] -> TreePos Empty BBLabel -> Either TreeError (TreePos Empty BBLabel) |
| 41 | rose' [] = return | 51 | rose' [] = return |
| 42 | rose' (x:xs) = (>>= rose' xs) . rose'' x | 52 | rose' (x:xs) = (>>= rose' xs) . rose'' x |
| 43 | 53 | ||
| 44 | rose'' (BBStr t) = return . Z.nextSpace . Z.insert (Node t []) | 54 | rose'' (BBStr t) = return . Z.nextSpace . Z.insert (Node (t, Map.empty) []) |
| 45 | rose'' (BBOpen t _) = return . Z.children . Z.insert (Node t []) | 55 | rose'' (BBOpen t attrs) = return . Z.children . Z.insert (Node (t, Map.fromList attrs) []) |
| 46 | rose'' (BBClose t) = close t -- for more pointless | 56 | rose'' (BBClose t) = close t -- for more pointless |
| 47 | 57 | ||
| 48 | close :: Text -> TreePos Empty Text -> Either TreeError (TreePos Empty Text) | 58 | close :: Text -> TreePos Empty BBLabel -> Either TreeError (TreePos Empty BBLabel) |
| 49 | close tag pos = do | 59 | close tag pos = do |
| 50 | pos' <- maybe (Left $ LeftoverClose tag) Right $ Z.parent pos | 60 | pos' <- maybe (Left $ LeftoverClose tag) Right $ Z.parent pos |
| 51 | unless (Z.label pos' `matches` tag) . Left $ ImbalancedTags (Z.label pos') tag -- The structure shows that this mode of failure is not logically required -- it's just nice to have | 61 | let |
| 62 | pTag = fst $ Z.label pos' | ||
| 63 | unless (pTag `matches` tag) . Left $ ImbalancedTags pTag tag -- The structure shows that this mode of failure is not logically required -- it's just nice to have | ||
| 52 | return $ Z.nextSpace pos' | 64 | return $ Z.nextSpace pos' |
diff --git a/bbcode/src/Text/BBCode/Lexer.hs b/bbcode/src/Text/BBCode/Lexer.hs index ad26113..2eb0022 100644 --- a/bbcode/src/Text/BBCode/Lexer.hs +++ b/bbcode/src/Text/BBCode/Lexer.hs | |||
| @@ -29,9 +29,12 @@ data BBToken = BBOpen Text [(Text, Maybe Text)] -- ^ Tag open with attributes | |||
| 29 | token :: Parser BBToken | 29 | token :: Parser BBToken |
| 30 | -- ^ Tokenizer | 30 | -- ^ Tokenizer |
| 31 | token = BBClose <$> ("[/" *> escapedText' [']'] <* "]") | 31 | token = BBClose <$> ("[/" *> escapedText' [']'] <* "]") |
| 32 | <|> BBOpen <$> ("[" *> escapedText' [']', ' ']) <*> (option [] attrs <* "]") | 32 | <|> uncurry BBOpen <$> openTag |
| 33 | <|> BBStr <$> escapedText ['['] | 33 | <|> BBStr <$> escapedText ['['] |
| 34 | 34 | ||
| 35 | openTag :: Parser (Text, [(Text, Maybe Text)]) | ||
| 36 | openTag = (,) <$> ("[" *> escapedText' [']', ' ']) <*> (option [] attrs <* "]") | ||
| 37 | |||
| 35 | attrs :: Parser [(Text, Maybe Text)] | 38 | attrs :: Parser [(Text, Maybe Text)] |
| 36 | attrs = (:) <$> (attrs' <* takeWhile isSpace) <*> (option [] $ attrs) | 39 | attrs = (:) <$> (attrs' <* takeWhile isSpace) <*> (option [] $ attrs) |
| 37 | where | 40 | where |
diff --git a/bbcode/thermoprint-bbcode.cabal b/bbcode/thermoprint-bbcode.cabal index 80a9da5..f25ffdf 100644 --- a/bbcode/thermoprint-bbcode.cabal +++ b/bbcode/thermoprint-bbcode.cabal | |||
| @@ -26,6 +26,7 @@ library | |||
| 26 | , text >=1.2.1 && <2 | 26 | , text >=1.2.1 && <2 |
| 27 | , containers >=0.4.0 && <1 | 27 | , containers >=0.4.0 && <1 |
| 28 | , rosezipper >=0.2 && <1 | 28 | , rosezipper >=0.2 && <1 |
| 29 | , case-insensitive >=1.2.0 && <2 | ||
| 29 | hs-source-dirs: src | 30 | hs-source-dirs: src |
| 30 | default-language: Haskell2010 | 31 | default-language: Haskell2010 |
| 31 | 32 | ||
diff --git a/bbcode/thermoprint-bbcode.nix b/bbcode/thermoprint-bbcode.nix index c379053..896bb04 100644 --- a/bbcode/thermoprint-bbcode.nix +++ b/bbcode/thermoprint-bbcode.nix | |||
| @@ -1,12 +1,12 @@ | |||
| 1 | { mkDerivation, attoparsec, base, containers, hspec, QuickCheck | 1 | { mkDerivation, attoparsec, base, case-insensitive, containers |
| 2 | , quickcheck-instances, rosezipper, stdenv, text | 2 | , hspec, QuickCheck, quickcheck-instances, rosezipper, stdenv, text |
| 3 | }: | 3 | }: |
| 4 | mkDerivation { | 4 | mkDerivation { |
| 5 | pname = "thermoprint-bbcode"; | 5 | pname = "thermoprint-bbcode"; |
| 6 | version = "0.0.0"; | 6 | version = "0.0.0"; |
| 7 | src = ./.; | 7 | src = ./.; |
| 8 | libraryHaskellDepends = [ | 8 | libraryHaskellDepends = [ |
| 9 | attoparsec base containers rosezipper text | 9 | attoparsec base case-insensitive containers rosezipper text |
| 10 | ]; | 10 | ]; |
| 11 | testHaskellDepends = [ | 11 | testHaskellDepends = [ |
| 12 | attoparsec base hspec QuickCheck quickcheck-instances text | 12 | attoparsec base hspec QuickCheck quickcheck-instances text |
