diff options
author | Gregor Kleen <gkleen@yggdrasil.li> | 2016-01-18 05:42:26 +0000 |
---|---|---|
committer | Gregor Kleen <gkleen@yggdrasil.li> | 2016-01-18 05:42:26 +0000 |
commit | 4914e4d7f638b7771ecdaf24ee0516c05505697c (patch) | |
tree | 6c89d30ae2ea97f14f4a35af9013dc74e747492c /bbcode | |
parent | 539b69b3e3b0c4ffee2323a8fe6239ebda4af48c (diff) | |
download | thermoprint-4914e4d7f638b7771ecdaf24ee0516c05505697c.tar thermoprint-4914e4d7f638b7771ecdaf24ee0516c05505697c.tar.gz thermoprint-4914e4d7f638b7771ecdaf24ee0516c05505697c.tar.bz2 thermoprint-4914e4d7f638b7771ecdaf24ee0516c05505697c.tar.xz thermoprint-4914e4d7f638b7771ecdaf24ee0516c05505697c.zip |
Disallow paragraphs everywhere but at the top
Diffstat (limited to 'bbcode')
-rw-r--r-- | bbcode/bbcode.cabal | 2 | ||||
-rw-r--r-- | bbcode/bbcode.nix | 2 | ||||
-rw-r--r-- | bbcode/src/Text/BBCode.hs | 37 | ||||
-rw-r--r-- | bbcode/test/Text/BBCodeSpec.hs | 42 |
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 | ||
4 | name: bbcode | 4 | name: bbcode |
5 | version: 0.0.0 | 5 | version: 1.0.0 |
6 | synopsis: A parser for bbcode | 6 | synopsis: A parser for bbcode |
7 | -- description: | 7 | -- description: |
8 | homepage: http://dirty-haskell.org/tags/thermoprint.html | 8 | homepage: 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 | }: |
4 | mkDerivation { | 4 | mkDerivation { |
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 |
42 | data DomTree = Element (CI Text) (Map (CI Text) Text) [DomTree] | 43 | data 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 | ||
47 | dom :: Forest BBLabel -> [DomTree] | 47 | -- | List of paragraphs, which are comprised of lists of 'DomTree's |
48 | type DomForest = [[DomTree]] | ||
49 | |||
50 | dom :: 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 |
51 | dom = map dom' | 54 | dom = 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 |
58 | data BBCodeError = LexerError String -- ^ Error while parsing input to stream of tokens | 63 | data 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 | ||
62 | instance Exception BBCodeError | 67 | instance Exception BBCodeError |
63 | 68 | ||
64 | bbcode :: Text -> Either BBCodeError [DomTree] | 69 | bbcode :: Text -> Either BBCodeError DomForest |
65 | -- ^ Parse BBCode | 70 | -- ^ Parse BBCode |
66 | bbcode t = fmap dom $ first LexerError (parseOnly (many token <* endOfInput) t) >>= first TreeError . rose | 71 | bbcode 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 |
69 | data TreeError = MismatchedTags Text Text -- ^ Closing tags label does not match opening tags | 74 | data 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 | ||
73 | instance Exception TreeError | 79 | instance Exception TreeError |
@@ -93,7 +99,7 @@ rose :: [BBToken] -> Either TreeError (Forest BBLabel) | |||
93 | rose = fmap Z.toForest . foldM (flip rose') (Z.fromForest []) | 99 | rose = 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 | ||
23 | examples :: [(Text, [DomTree])] | 23 | examples :: [(Text, DomForest)] |
24 | examples = [ ("[t]test[/t]" | 24 | examples = [ ("[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 | ||