aboutsummaryrefslogtreecommitdiff
path: root/bbcode/src/Text/BBCode.hs
blob: a6de7b49500cd565afcc2fd5dc872e278500d6c4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}

module Text.BBCode
       ( TreeError(..)
       , rose
       , matches
       ) where

import Data.Text (Text)

import GHC.Generics (Generic)
import Control.Exception (Exception)
import Data.Typeable (Typeable)

import Control.Monad (unless)

import Text.BBCode.Lexer (BBToken(..), token)

import Data.Tree
import Data.Tree.Zipper (TreePos, Empty, Full)
import qualified Data.Tree.Zipper as Z

data TreeError = ImbalancedTags Text Text
               | LeftoverClose Text
               deriving (Show, Eq, Generic, Typeable)

instance Exception TreeError

matches :: Text -> Text -> Bool
-- ^ @`matches` "open" "close"@ should be true iff @[/close]@ is a valid closing tag for @[open]@
--
-- Until we allow for attributes this is equality according to `(==)`
matches = (==)

rose :: [BBToken] -> Either TreeError (Forest Text)
-- ^ Assuming that both tags and content have the same type (we use 'Text') bbcode is a flat representation of a rose tree
rose = fmap Z.toForest . flip rose' (Z.fromForest [])
  where
    rose' :: [BBToken] -> TreePos Empty Text -> Either TreeError (TreePos Empty Text)
    rose' [] = return
    rose' (x:xs) = (>>= rose' xs) . rose'' x

    rose'' (BBStr t) = return . Z.nextSpace . Z.insert (Node t [])
    rose'' (BBOpen t _) = return . Z.children . Z.insert (Node t [])
    rose'' (BBClose t) = close t -- for more pointless

    close :: Text -> TreePos Empty Text -> Either TreeError (TreePos Empty Text)
    close tag pos = do
      pos' <- maybe (Left $ LeftoverClose tag) Right $ Z.parent pos
      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
      return $ Z.nextSpace pos'