aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregor Kleen <gkleen@yggdrasil.li>2016-01-18 05:42:26 +0000
committerGregor Kleen <gkleen@yggdrasil.li>2016-01-18 05:42:26 +0000
commit4914e4d7f638b7771ecdaf24ee0516c05505697c (patch)
tree6c89d30ae2ea97f14f4a35af9013dc74e747492c
parent539b69b3e3b0c4ffee2323a8fe6239ebda4af48c (diff)
downloadthermoprint-4914e4d7f638b7771ecdaf24ee0516c05505697c.tar
thermoprint-4914e4d7f638b7771ecdaf24ee0516c05505697c.tar.gz
thermoprint-4914e4d7f638b7771ecdaf24ee0516c05505697c.tar.bz2
thermoprint-4914e4d7f638b7771ecdaf24ee0516c05505697c.tar.xz
thermoprint-4914e4d7f638b7771ecdaf24ee0516c05505697c.zip
Disallow paragraphs everywhere but at the top
-rw-r--r--bbcode/bbcode.cabal2
-rw-r--r--bbcode/bbcode.nix2
-rw-r--r--bbcode/src/Text/BBCode.hs37
-rw-r--r--bbcode/test/Text/BBCodeSpec.hs42
4 files changed, 44 insertions, 39 deletions
diff --git a/bbcode/bbcode.cabal b/bbcode/bbcode.cabal
index d2f4932..f899d49 100644
--- a/bbcode/bbcode.cabal
+++ b/bbcode/bbcode.cabal
@@ -2,7 +2,7 @@
2-- documentation, see http://haskell.org/cabal/users-guide/ 2-- documentation, see http://haskell.org/cabal/users-guide/
3 3
4name: bbcode 4name: bbcode
5version: 0.0.0 5version: 1.0.0
6synopsis: A parser for bbcode 6synopsis: A parser for bbcode
7-- description: 7-- description:
8homepage: http://dirty-haskell.org/tags/thermoprint.html 8homepage: http://dirty-haskell.org/tags/thermoprint.html
diff --git a/bbcode/bbcode.nix b/bbcode/bbcode.nix
index 82ba2f4..97f8982 100644
--- a/bbcode/bbcode.nix
+++ b/bbcode/bbcode.nix
@@ -3,7 +3,7 @@
3}: 3}:
4mkDerivation { 4mkDerivation {
5 pname = "bbcode"; 5 pname = "bbcode";
6 version = "0.0.0"; 6 version = "1.0.0";
7 src = ./.; 7 src = ./.;
8 libraryHaskellDepends = [ 8 libraryHaskellDepends = [
9 attoparsec base case-insensitive containers rosezipper text 9 attoparsec base case-insensitive containers rosezipper text
diff --git a/bbcode/src/Text/BBCode.hs b/bbcode/src/Text/BBCode.hs
index fbd8a33..8f04f19 100644
--- a/bbcode/src/Text/BBCode.hs
+++ b/bbcode/src/Text/BBCode.hs
@@ -6,6 +6,7 @@ module Text.BBCode
6 ( bbcode 6 ( bbcode
7 , BBCodeError(..) 7 , BBCodeError(..)
8 , TreeError(..) 8 , TreeError(..)
9 , DomForest
9 , DomTree(..) 10 , DomTree(..)
10 -- , dom 11 -- , dom
11 -- , BBLabel 12 -- , BBLabel
@@ -40,19 +41,23 @@ import Data.Bifunctor (Bifunctor(first))
40 41
41-- | Our target structure -- a rose tree with an explicit terminal constructor 42-- | Our target structure -- a rose tree with an explicit terminal constructor
42data DomTree = Element (CI Text) (Map (CI Text) Text) [DomTree] 43data DomTree = Element (CI Text) (Map (CI Text) Text) [DomTree]
43 | Paragraph [DomTree]
44 | Content Text 44 | Content Text
45 deriving (Show, Eq) 45 deriving (Show, Eq)
46 46
47dom :: Forest BBLabel -> [DomTree] 47-- | List of paragraphs, which are comprised of lists of 'DomTree's
48type DomForest = [[DomTree]]
49
50dom :: Forest BBLabel -> DomForest
48-- ^ Parse semantically constrained rose tree to syntactically constrained version 51-- ^ Parse semantically constrained rose tree to syntactically constrained version
49-- 52--
50-- Silently drops children of semantically terminal nodes ('BBPlain') 53-- Silently drops children of semantically terminal nodes ('BBPlain') and paragraphs anywhere but at top level
51dom = map dom' 54dom = map fromPar
52 where 55 where
53 dom' (Node (BBPlain t) _) = Content t 56 fromPar (Node BBPar ts) = concatMap dom' ts
54 dom' (Node (BBTag t attrs) ts) = Element (CI.mk t) (Map.mapKeys CI.mk attrs) $ map dom' ts 57 fromPar x = dom' x
55 dom' (Node BBPar ts) = Paragraph $ map dom' ts 58 dom' (Node (BBPlain t) _) = pure $ Content t
59 dom' (Node (BBTag t attrs) ts) = pure . Element (CI.mk t) (Map.mapKeys CI.mk attrs) $ concatMap dom' ts
60 dom' (Node BBPar _) = []
56 61
57-- | Errors encountered during parsing 62-- | Errors encountered during parsing
58data BBCodeError = LexerError String -- ^ Error while parsing input to stream of tokens 63data BBCodeError = LexerError String -- ^ Error while parsing input to stream of tokens
@@ -61,13 +66,14 @@ data BBCodeError = LexerError String -- ^ Error while parsing input to stream of
61 66
62instance Exception BBCodeError 67instance Exception BBCodeError
63 68
64bbcode :: Text -> Either BBCodeError [DomTree] 69bbcode :: Text -> Either BBCodeError DomForest
65-- ^ Parse BBCode 70-- ^ Parse BBCode
66bbcode t = fmap dom $ first LexerError (parseOnly (many token <* endOfInput) t) >>= first TreeError . rose 71bbcode t = fmap dom $ first LexerError (parseOnly (many token <* endOfInput) t) >>= first TreeError . rose
67 72
68-- | Errors in input encountered during parsing of lexed token-stream 73-- | Errors in input encountered during parsing of lexed token-stream
69data TreeError = MismatchedTags Text Text -- ^ Closing tags label does not match opening tags 74data TreeError = MismatchedTags Text Text -- ^ Closing tags label does not match opening tags
70 | ImbalancedTags Text -- ^ We found an extraneous closing tag 75 | ImbalancedTags Text -- ^ We found an extraneous closing tag
76 | ParagraphWithinTag -- ^ We found a paragraph-break within a tag
71 deriving (Show, Eq, Generic, Typeable) 77 deriving (Show, Eq, Generic, Typeable)
72 78
73instance Exception TreeError 79instance Exception TreeError
@@ -93,7 +99,7 @@ rose :: [BBToken] -> Either TreeError (Forest BBLabel)
93rose = fmap Z.toForest . foldM (flip rose') (Z.fromForest []) 99rose = fmap Z.toForest . foldM (flip rose') (Z.fromForest [])
94 where 100 where
95 rose' (BBStr t) = return . Z.nextSpace . Z.insert (Node (BBPlain t) []) 101 rose' (BBStr t) = return . Z.nextSpace . Z.insert (Node (BBPlain t) [])
96 rose' BBNewPar = return . parBreak -- for more pointless 102 rose' BBNewPar = parBreak -- for more pointless
97 rose' (BBOpen t attrs) = return . Z.children . Z.insert (Node (BBTag t $ Map.fromList attrs) []) 103 rose' (BBOpen t attrs) = return . Z.children . Z.insert (Node (BBTag t $ Map.fromList attrs) [])
98 rose' (BBContained t attrs) = return . Z.nextSpace . Z.insert (Node (BBTag t $ Map.fromList attrs) []) 104 rose' (BBContained t attrs) = return . Z.nextSpace . Z.insert (Node (BBTag t $ Map.fromList attrs) [])
99 rose' (BBClose t) = close t -- for more pointless 105 rose' (BBClose t) = close t -- for more pointless
@@ -110,15 +116,14 @@ rose = fmap Z.toForest . foldM (flip rose') (Z.fromForest [])
110 | isPar . Z.tree $ pos = Z.parent pos >>= traversePars 116 | isPar . Z.tree $ pos = Z.parent pos >>= traversePars
111 | otherwise = return pos 117 | otherwise = return pos
112 118
113 parBreak :: TreePos Empty BBLabel -> TreePos Empty BBLabel 119 parBreak :: TreePos Empty BBLabel -> Either TreeError (TreePos Empty BBLabel)
114 parBreak z = let siblings = reverse $ Z.before z -- We only move ever move right so Z.after will always be empty 120 parBreak z = let siblings = reverse $ Z.before z
115 121
116 siblingsAsPars 122 siblingsAsPars
117 | all isPar siblings = z 123 | all isPar siblings = Right z
118 | otherwise = case Z.parent z of 124 | Z.isRoot z = Right . Z.fromForest $ [Node BBPar siblings]
119 Nothing -> Z.fromForest $ [Node BBPar siblings] 125 | otherwise = Left ParagraphWithinTag
120 Just p -> Z.children . Z.modifyTree (\(Node l _) -> Node l [Node BBPar siblings]) $ p 126 in Z.children . Z.insert (Node BBPar []) . Z.last <$> siblingsAsPars
121 in Z.children . Z.insert (Node BBPar []) . Z.last $ siblingsAsPars
122 127
123 isPar (Node BBPar _) = True 128 isPar (Node BBPar _) = True
124 isPar _ = False 129 isPar _ = False
diff --git a/bbcode/test/Text/BBCodeSpec.hs b/bbcode/test/Text/BBCodeSpec.hs
index e24c661..241cd76 100644
--- a/bbcode/test/Text/BBCodeSpec.hs
+++ b/bbcode/test/Text/BBCodeSpec.hs
@@ -20,44 +20,44 @@ spec = do
20 example n (s, ts) = let str = "Example " <> show n 20 example n (s, ts) = let str = "Example " <> show n
21 in specify str (bbcode s == Right ts) 21 in specify str (bbcode s == Right ts)
22 22
23examples :: [(Text, [DomTree])] 23examples :: [(Text, DomForest)]
24examples = [ ("[t]test[/t]" 24examples = [ ("[t]test[/t]"
25 , [Element "t" [] [Content "test"]]) 25 , pure [Element "t" [] [Content "test"]])
26 , ("[t]te\\st[/t]" 26 , ("[t]te\\st[/t]"
27 , [Element "t" [] [Content "te\\st"]]) 27 , pure [Element "t" [] [Content "te\\st"]])
28 , ("[t]te\\[st[/t]" 28 , ("[t]te\\[st[/t]"
29 , [Element "t" [] [Content "te[st"]]) 29 , pure [Element "t" [] [Content "te[st"]])
30 , ("[t]test\\\\[/t]" 30 , ("[t]test\\\\[/t]"
31 , [Element "t" [] [Content "test\\"]]) 31 , pure [Element "t" [] [Content "test\\"]])
32 , ("[\\t]test[/\\t]" 32 , ("[\\t]test[/\\t]"
33 , [Element "\\t" [] [Content "test"]]) 33 , pure [Element "\\t" [] [Content "test"]])
34 , ("[t attr]test[/t]" 34 , ("[t attr]test[/t]"
35 , [Element "t" [("attr", "")] [Content "test"]]) 35 , pure [Element "t" [("attr", "")] [Content "test"]])
36 , ("[t=attr]test[/t]" 36 , ("[t=attr]test[/t]"
37 , [Element "t" [("", "attr")] [Content "test"]]) 37 , pure [Element "t" [("", "attr")] [Content "test"]])
38 , ("[t attr=val]test[/t]" 38 , ("[t attr=val]test[/t]"
39 , [Element "t" [("attr", "val")] [Content "test"]]) 39 , pure [Element "t" [("attr", "val")] [Content "test"]])
40 , ("[t attr=\"val\"]test[/t]" 40 , ("[t attr=\"val\"]test[/t]"
41 , [Element "t" [("attr", "val")] [Content "test"]]) 41 , pure [Element "t" [("attr", "val")] [Content "test"]])
42 , ("[t attr=\"va]l\"]test[/t]" 42 , ("[t attr=\"va]l\"]test[/t]"
43 , [Element "t" [("attr", "va]l")] [Content "test"]]) 43 , pure [Element "t" [("attr", "va]l")] [Content "test"]])
44 , ("[t attr=\"va\\\"l\"]test[/t]" 44 , ("[t attr=\"va\\\"l\"]test[/t]"
45 , [Element "t" [("attr", "va\"l")] [Content "test"]]) 45 , pure [Element "t" [("attr", "va\"l")] [Content "test"]])
46 , ("[t attr=\"val\" attr2=\"val2\" ]test[/t]" 46 , ("[t attr=\"val\" attr2=\"val2\" ]test[/t]"
47 , [Element "t" [("attr", "val"), ("attr2", "val2")] [Content "test"]]) 47 , pure [Element "t" [("attr", "val"), ("attr2", "val2")] [Content "test"]])
48 , ("[br/]" 48 , ("[br/]"
49 , [Element "br" [] []]) 49 , pure [Element "br" [] []])
50 , ("[br attr/]" 50 , ("[br attr/]"
51 , [Element "br" [("attr", "")] []]) 51 , pure [Element "br" [("attr", "")] []])
52 , ("[br=val/]" 52 , ("[br=val/]"
53 , [Element "br" [("", "val")] []]) 53 , pure [Element "br" [("", "val")] []])
54 , ("[br attr=val/]" 54 , ("[br attr=val/]"
55 , [Element "br" [("attr", "val")] []]) 55 , pure [Element "br" [("attr", "val")] []])
56 , ("[br attr=val val2/]" 56 , ("[br attr=val val2/]"
57 , [Element "br" [("attr", "val"), ("val2", "")] []]) 57 , pure [Element "br" [("attr", "val"), ("val2", "")] []])
58 , ("foo\n\nbar" 58 , ("foo\n\nbar"
59 , [Paragraph [Content "foo"], Paragraph [Content "bar"]]) 59 , [[Content "foo"], [Content "bar"]])
60 , ("[b]foo\n\nbar[/b]" 60 , ("[b]foo[/b]\n\n[b]bar[/b]"
61 , [Element "b" [] [Paragraph [Content "foo"], Paragraph [Content "bar"]]]) 61 , [[Element "b" [] [Content "foo"]], [Element "b" [] [Content "bar"]]])
62 ] 62 ]
63 63