diff options
author | Gregor Kleen <gkleen@yggdrasil.li> | 2015-12-25 17:56:13 +0000 |
---|---|---|
committer | Gregor Kleen <gkleen@yggdrasil.li> | 2015-12-25 17:56:13 +0000 |
commit | 9db2c42f4880362cf098358de830415c14f6878c (patch) | |
tree | 2b0b9257f01eec926152746fc2e7646764063c3a /bbcode/src | |
parent | 08eee2f0de77ffa631e84ccf734e8e95817b7c81 (diff) | |
download | thermoprint-9db2c42f4880362cf098358de830415c14f6878c.tar thermoprint-9db2c42f4880362cf098358de830415c14f6878c.tar.gz thermoprint-9db2c42f4880362cf098358de830415c14f6878c.tar.bz2 thermoprint-9db2c42f4880362cf098358de830415c14f6878c.tar.xz thermoprint-9db2c42f4880362cf098358de830415c14f6878c.zip |
Cleaned tree for rewrite
Diffstat (limited to 'bbcode/src')
-rw-r--r-- | bbcode/src/BBCode.hs | 120 | ||||
-rw-r--r-- | bbcode/src/BBCode/Syntax.hs | 108 | ||||
-rw-r--r-- | bbcode/src/BBCode/Tokenizer.hs | 44 |
3 files changed, 0 insertions, 272 deletions
diff --git a/bbcode/src/BBCode.hs b/bbcode/src/BBCode.hs deleted file mode 100644 index 087842f..0000000 --- a/bbcode/src/BBCode.hs +++ /dev/null | |||
@@ -1,120 +0,0 @@ | |||
1 | {-# LANGUAGE OverloadedStrings, OverloadedLists, RankNTypes, ImpredicativeTypes #-} | ||
2 | |||
3 | module BBCode | ||
4 | ( parse | ||
5 | , make | ||
6 | ) where | ||
7 | |||
8 | import Thermoprint | ||
9 | |||
10 | import BBCode.Tokenizer | ||
11 | import BBCode.Syntax | ||
12 | |||
13 | import Data.Maybe | ||
14 | import Data.Monoid | ||
15 | import Data.Either | ||
16 | import Data.Bifunctor | ||
17 | import Data.List | ||
18 | import Data.Ord | ||
19 | import Data.Foldable | ||
20 | |||
21 | import Data.Function (on) | ||
22 | |||
23 | import Data.CaseInsensitive ( CI ) | ||
24 | import qualified Data.CaseInsensitive as CI | ||
25 | |||
26 | import Data.Map ( Map ) | ||
27 | import qualified Data.Map as Map | ||
28 | |||
29 | import Prelude hiding (takeWhile) | ||
30 | |||
31 | import Debug.Trace | ||
32 | |||
33 | knownTags :: Map (CI String) (Either (Block String -> Block String) (Inline String -> Inline String)) | ||
34 | knownTags = [ ("center", Left Center) | ||
35 | , ("u", Right Underline) | ||
36 | ] | ||
37 | |||
38 | isBlock, isInline :: String -> Bool | ||
39 | isBlock = testTag isLeft | ||
40 | isInline = testTag isRight | ||
41 | testTag f k = fromMaybe False (f <$> Map.lookup (CI.mk k) knownTags) | ||
42 | |||
43 | data Decorated c = Decorated c [String] | ||
44 | deriving (Show, Eq) | ||
45 | |||
46 | make :: Block String -> String | ||
47 | make (Over blocks) = concat $ map make blocks | ||
48 | make (Center block) = "[center]" ++ make block ++ "[/center]\n" | ||
49 | make (Paragraph inline) = make' inline ++ "\n" | ||
50 | |||
51 | make' :: Inline String -> String | ||
52 | make' (Beside inlines) = concat $ map make' inlines | ||
53 | make' (Underline inline) = "[u]" ++ make' inline ++ "[/u]" | ||
54 | make' (Cooked c) = c | ||
55 | make' (Raw _) = error "Cannot transform block containing raw data to bbcode" | ||
56 | |||
57 | parse :: String -> Either String (Block String) | ||
58 | parse input = massage <$> ((remerge . blockify <$> (tokenize input >>= treeify)) >>= semantics) | ||
59 | |||
60 | massage :: Block String -> Block String | ||
61 | massage (Over [x]) = massage x | ||
62 | massage (Over xs) = Over $ map massage xs | ||
63 | massage (Center x) = Center $ massage x | ||
64 | massage (Paragraph x) = Paragraph $ massage' x | ||
65 | |||
66 | massage' :: Inline String -> Inline String | ||
67 | massage' (Beside [x]) = massage' x | ||
68 | massage' (Beside xs) = Beside $ map massage' xs | ||
69 | massage' (Underline x) = Underline $ massage' x | ||
70 | massage' z = z | ||
71 | |||
72 | blockify :: ContentForest -> [Decorated String] | ||
73 | blockify = map sortDeco . concat . map (blockify' []) | ||
74 | where | ||
75 | blockify' _ Empty = [] | ||
76 | blockify' initial (Content str) = [str `Decorated` initial] | ||
77 | blockify' initial (Tagged cs tag) = concat $ map (blockify' $ tag : initial) cs | ||
78 | sortDeco :: Decorated c -> Decorated c | ||
79 | sortDeco (Decorated c tags) = Decorated c $ (nubBy ((==) `on` CI.mk) . sortBy blockiness) tags | ||
80 | blockiness :: String -> String -> Ordering | ||
81 | blockiness a b = comparing isBlock a b <> comparing CI.mk a b -- sort also alphabetically for the benefit of remerge | ||
82 | |||
83 | remerge :: [Decorated String] -> ContentForest | ||
84 | remerge [] = [] | ||
85 | remerge [(Decorated c deco)] = [appEndo applyDeco $ Content c] | ||
86 | where | ||
87 | applyDeco = foldMap (\t -> Endo (\c -> Tagged [c] t)) $ reverse deco | ||
88 | remerge xs = concat $ map toTree $ groupLasts xs | ||
89 | where | ||
90 | groupLasts = groupBy ((==) `on` (listToMaybe . reverse . (\(Decorated _ ds) -> ds))) | ||
91 | -- toTree :: [Decorated String] -> [ContentTree] | ||
92 | toTree [] = [] | ||
93 | toTree (x@(Decorated _ []):xs) = map (Content . (\(Decorated c []) -> c)) (x:xs) | ||
94 | toTree (x@(Decorated _ ds):xs) = [Tagged content tag] | ||
95 | where | ||
96 | tag = last ds | ||
97 | content = concat $ map toTree $ groupLasts $ map stripLast (x:xs) | ||
98 | stripLast (Decorated c ds) = Decorated c (init ds) | ||
99 | |||
100 | unknownTag :: forall a. String -> Either String a | ||
101 | unknownTag tag = Left $ "Unknown tag: " ++ tag | ||
102 | |||
103 | semantics :: ContentForest -> Either String (Block String) | ||
104 | semantics forest = Over <$> mapM semantics' forest | ||
105 | |||
106 | semantics' :: ContentTree -> Either String (Block String) | ||
107 | semantics' Empty = Right $ Over [] | ||
108 | semantics' (Content str) = Right $ Paragraph (Cooked str) | ||
109 | semantics' (Tagged cs tag) = case Map.lookup (CI.mk tag) knownTags of | ||
110 | Nothing -> unknownTag tag | ||
111 | Just (Left f) -> Over . map f <$> mapM semantics' cs | ||
112 | Just (Right f) -> Paragraph . Beside . map f<$> mapM iSemantics cs | ||
113 | |||
114 | iSemantics :: ContentTree -> Either String (Inline String) | ||
115 | iSemantics Empty = Right $ Beside [] | ||
116 | iSemantics (Content str) = Right $ Cooked str | ||
117 | iSemantics (Tagged cs tag) = case Map.lookup (CI.mk tag) knownTags of | ||
118 | Nothing -> unknownTag tag | ||
119 | Just (Left f) -> error "Known inline tag sorted within block" | ||
120 | Just (Right f) -> Beside . map f <$> mapM iSemantics cs | ||
diff --git a/bbcode/src/BBCode/Syntax.hs b/bbcode/src/BBCode/Syntax.hs deleted file mode 100644 index a196e05..0000000 --- a/bbcode/src/BBCode/Syntax.hs +++ /dev/null | |||
@@ -1,108 +0,0 @@ | |||
1 | {-# LANGUAGE RecordWildCards #-} | ||
2 | |||
3 | module BBCode.Syntax | ||
4 | ( treeify | ||
5 | , ContentForest | ||
6 | , ContentTree(..) | ||
7 | ) where | ||
8 | |||
9 | import BBCode.Tokenizer (Token(..)) | ||
10 | |||
11 | import Data.Foldable | ||
12 | import Data.Bifunctor | ||
13 | import Data.Monoid | ||
14 | |||
15 | import Control.Monad.State | ||
16 | import Control.Monad.Trans | ||
17 | import Control.Monad | ||
18 | |||
19 | import Data.CaseInsensitive ( CI ) | ||
20 | import qualified Data.CaseInsensitive as CI | ||
21 | |||
22 | import Data.Function (on) | ||
23 | |||
24 | type ContentForest = [ContentTree] | ||
25 | data ContentTree = Content String | ||
26 | | Tagged [ContentTree] String | ||
27 | | Empty | ||
28 | deriving (Show, Eq) | ||
29 | |||
30 | data Step = Down [ContentTree] [ContentTree] String | ||
31 | deriving (Show) | ||
32 | |||
33 | data Zipper = Zipper | ||
34 | { hole :: String | ||
35 | , prevs :: [ContentTree] | ||
36 | , steps :: [Step] | ||
37 | } | ||
38 | deriving (Show) | ||
39 | |||
40 | type Parser = StateT Zipper (Either String) | ||
41 | abort :: String -> Parser a | ||
42 | abort = lift . Left | ||
43 | |||
44 | |||
45 | treeify :: [Token] -> Either String ContentForest | ||
46 | treeify tokenStream = second (postProcess . unZip) $ execStateT (mapM_ incorporate tokenStream) (Zipper "" [] []) | ||
47 | |||
48 | postProcess :: ContentForest -> ContentForest | ||
49 | postProcess forest = do | ||
50 | tree <- forest | ||
51 | let | ||
52 | tree' = postProcess' tree | ||
53 | guard $ tree' /= Empty | ||
54 | return tree' | ||
55 | where | ||
56 | postProcess' :: ContentTree -> ContentTree | ||
57 | postProcess' (Content "") = Empty | ||
58 | postProcess' (Tagged [] _) = Empty | ||
59 | postProcess' (Tagged cs t) = Tagged [c' | c <- cs, let c' = postProcess' c, c' /= Empty ] t | ||
60 | postProcess' x = x | ||
61 | |||
62 | unZip :: Zipper -> ContentForest | ||
63 | unZip Zipper{..} = reverse (apply hole steps : prevs) | ||
64 | |||
65 | apply :: String -> [Step] -> ContentTree | ||
66 | apply hole steps = hole `apply'` (reverse steps) | ||
67 | apply' "" [] = Empty | ||
68 | apply' hole [] = Content hole | ||
69 | apply' hole (Down pres posts tag:steps) = Tagged (reverse pres ++ [hole `apply` steps] ++ posts) tag | ||
70 | |||
71 | incorporate :: Token -> Parser () | ||
72 | incorporate (Text str) = append str | ||
73 | incorporate (Whitespace str) | ||
74 | | delimitsPar str = do | ||
75 | currSteps <- gets steps | ||
76 | if null currSteps then | ||
77 | modify (\Zipper{..} -> Zipper { hole = "", steps = [], prevs = (hole `apply` steps : prevs) }) | ||
78 | else | ||
79 | append str | ||
80 | | otherwise = append str | ||
81 | where | ||
82 | delimitsPar str = (sum . map (const (1 :: Integer)) . filter (== '\n') $ str) >= 1 | ||
83 | incorporate (TagOpen tagName) = modify $ (\z@(Zipper{..}) -> z { steps = (Down [] [] tagName : steps) }) | ||
84 | incorporate (TagClose tagName) = do | ||
85 | currSteps <- gets steps | ||
86 | case currSteps of | ||
87 | [] -> abort $ "Closing unopenend tag: " ++ tagName | ||
88 | (Down pres posts tagName':s) -> if ((==) `on` CI.mk) tagName tagName' then | ||
89 | goUp | ||
90 | else | ||
91 | abort $ "Mismatched tags: [" ++ tagName' ++ "]...[/" ++ tagName ++ "]" | ||
92 | |||
93 | append :: String -> Parser () | ||
94 | append str = modify (\z@(Zipper{..}) -> z { hole = hole ++ str }) | ||
95 | |||
96 | goUp :: Parser () | ||
97 | goUp = do | ||
98 | (Down pres posts tagName:s) <- gets steps | ||
99 | hole <- gets hole | ||
100 | let | ||
101 | steppedHole = Tagged (reverse pres ++ [Content hole] ++ posts) tagName | ||
102 | case s of | ||
103 | [] -> modify $ (\z@(Zipper{..}) -> Zipper { hole = "", prevs = steppedHole : prevs, steps = [] }) | ||
104 | (t:ts) -> do | ||
105 | let | ||
106 | (Down pres posts tagName) = t | ||
107 | t' = Down (steppedHole : pres) posts tagName | ||
108 | modify $ (\z -> z { hole = "", steps = (t':ts) }) | ||
diff --git a/bbcode/src/BBCode/Tokenizer.hs b/bbcode/src/BBCode/Tokenizer.hs deleted file mode 100644 index c860c7c..0000000 --- a/bbcode/src/BBCode/Tokenizer.hs +++ /dev/null | |||
@@ -1,44 +0,0 @@ | |||
1 | {-# LANGUAGE OverloadedStrings #-} | ||
2 | |||
3 | module BBCode.Tokenizer | ||
4 | ( Token(..) | ||
5 | , tokenize | ||
6 | ) where | ||
7 | |||
8 | import qualified Data.Text.Lazy as TL | ||
9 | import qualified Data.Text as T | ||
10 | |||
11 | import Control.Applicative | ||
12 | import Data.Attoparsec.Text.Lazy | ||
13 | |||
14 | import Data.Char (isSpace) | ||
15 | import Data.Monoid (mconcat) | ||
16 | |||
17 | data Token = Text String | ||
18 | | Whitespace String | ||
19 | | TagOpen String | ||
20 | | TagClose String | ||
21 | deriving (Show, Read, Eq) | ||
22 | |||
23 | tokenize :: String -> Either String [Token] | ||
24 | tokenize = eitherResult . parse (tokenize' <* endOfInput) . TL.pack | ||
25 | |||
26 | tokenize' :: Parser [Token] | ||
27 | tokenize' = many $ choice [ whitespace | ||
28 | , Text . T.unpack <$> ("\\" *> "[") | ||
29 | , tagClose | ||
30 | , tagOpen | ||
31 | , text | ||
32 | ] | ||
33 | |||
34 | whitespace :: Parser Token | ||
35 | whitespace = Whitespace <$> many1 space | ||
36 | |||
37 | tagOpen :: Parser Token | ||
38 | tagOpen = TagOpen . T.unpack <$> ("[" *> takeWhile1 (/= ']') <* "]") | ||
39 | |||
40 | tagClose :: Parser Token | ||
41 | tagClose = TagClose . T.unpack <$> ("[/" *> takeWhile1 (/= ']') <* "]") | ||
42 | |||
43 | text :: Parser Token | ||
44 | text = Text . T.unpack <$> takeWhile1 (\c -> not (isSpace c) && notInClass "[" c) | ||