diff options
Diffstat (limited to 'bbcode/src')
| -rw-r--r-- | bbcode/src/Text/BBCode.hs | 48 |
1 files changed, 43 insertions, 5 deletions
diff --git a/bbcode/src/Text/BBCode.hs b/bbcode/src/Text/BBCode.hs index 7a328a8..dfa1db7 100644 --- a/bbcode/src/Text/BBCode.hs +++ b/bbcode/src/Text/BBCode.hs | |||
| @@ -1,14 +1,52 @@ | |||
| 1 | {-# LANGUAGE OverloadedStrings #-} | 1 | {-# LANGUAGE OverloadedStrings #-} |
| 2 | {-# LANGUAGE DeriveGeneric #-} | ||
| 2 | 3 | ||
| 3 | module Text.BBCode | 4 | module Text.BBCode |
| 4 | ( | 5 | ( TreeError(..) |
| 6 | , rose | ||
| 7 | , matches | ||
| 5 | ) where | 8 | ) where |
| 6 | 9 | ||
| 7 | import Data.Attoparsec.Text | ||
| 8 | |||
| 9 | import Data.Text (Text) | 10 | import Data.Text (Text) |
| 10 | import qualified Data.Text as T (singleton, head, tail) | ||
| 11 | 11 | ||
| 12 | import Control.Applicative | 12 | import GHC.Generics (Generic) |
| 13 | import Control.Exception (Exception) | ||
| 14 | import Data.Typeable (Typeable) | ||
| 15 | |||
| 16 | import Control.Monad (unless) | ||
| 13 | 17 | ||
| 14 | import Text.BBCode.Lexer (BBToken(..), token) | 18 | import Text.BBCode.Lexer (BBToken(..), token) |
| 19 | |||
| 20 | import Data.Tree | ||
| 21 | import Data.Tree.Zipper (TreePos, Empty, Full) | ||
| 22 | import qualified Data.Tree.Zipper as Z | ||
| 23 | |||
| 24 | data TreeError = ImbalancedTags Text Text | ||
| 25 | | LeftoverClose Text | ||
| 26 | deriving (Show, Eq, Generic, Typeable) | ||
| 27 | |||
| 28 | instance Exception TreeError | ||
| 29 | |||
| 30 | matches :: Text -> Text -> Bool | ||
| 31 | -- ^ @`matches` "open" "close"@ should be true iff @[/close]@ is a valid closing tag for @[open]@ | ||
| 32 | -- | ||
| 33 | -- Until we allow for attributes this is equality according to `(==)` | ||
| 34 | matches = (==) | ||
| 35 | |||
| 36 | rose :: [BBToken] -> Either TreeError (Forest Text) | ||
| 37 | -- ^ 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 []) | ||
| 39 | where | ||
| 40 | rose' :: [BBToken] -> TreePos Empty Text -> Either TreeError (TreePos Empty Text) | ||
| 41 | rose' [] = return | ||
| 42 | rose' (x:xs) = (>>= rose' xs) . rose'' x | ||
| 43 | |||
| 44 | rose'' (BBStr t) = return . Z.nextSpace . Z.insert (Node t []) | ||
| 45 | rose'' (BBOpen t) = return . Z.children . Z.insert (Node t []) | ||
| 46 | rose'' (BBClose t) = close t -- for more pointless | ||
| 47 | |||
| 48 | close :: Text -> TreePos Empty Text -> Either TreeError (TreePos Empty Text) | ||
| 49 | close tag pos = do | ||
| 50 | 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 | ||
| 52 | return $ Z.nextSpace pos' | ||
