Wiki のために行指向パーサが欲しくなったので、Parsec を使って書いてみた。 手間取るかと思ったが Text.ParserCombinator.Parsec.Char のコードをそっくり真似て書いたらあっさり動いてしまった。
module LineParser (LineParser, indented, blank, firstChar, anyLine) where
import Text.ParserCombinators.Parsec.Prim
import Text.ParserCombinators.Parsec.Pos
import TextUtils
import Data.Char (isSpace)
type LineParser st a = GenParser String st a
indented = firstChar isSpace
blank = satisfy (null . strip)
firstChar f = satisfy (testFirstChar f)
testFirstChar f "" = False
testFirstChar f (c:_) = f c
anyLine = satisfy (const True)
satisfy :: (String -> Bool) -> LineParser st String
satisfy f = tokenPrim (\s -> show s)
(\pos s ss -> incSourceLine pos 1)
(\s -> if f s then Just s else Nothing)
さっそくこれを使って Wiki パーサを書きなおしてみた。 (以下パースのコードのみ抜粋)
compile :: String -> HTML
compile str = case parse document "" (lines str) of
Right ptext -> ptext
Left err -> p [Text (escape $ show err)] -- must not happen
document = do ss <- many block
return (concat ss)
block = (blank >> return [])
<|> heading
<|> ulist
<|> olist
<|> dlist
<|> preformatted
<|> paragraph
heading = do line <- firstChar (== '=')
let (mark, label) = span (== '=') line
return $ h (length mark) [Text (escape $ strip label)]
ulist = nestedList '*' ul
olist = nestedList '#' ol
nestedList mark xl = do items <- many1 itemLine
return $ compileList items
where
itemLine = do s <- firstChar (== mark)
ss <- many indented
return $ join (s:ss)
dlist = do lines <- many1 (firstChar (== ':'))
return $ dl (concatMap compileItem lines)
preformatted = do lines <- many1 indented
return $ pre [Text . escape . join . unindentBlock $ lines]
paragraph = do line <- anyLine
lines <- many (firstChar isNoFunc)
return $ p . compileText . join $ (line:lines)
where
isNoFunc = (`notElem` "=*#: \t\r\n\v\f")
すっきりした。
(23:27)
なにいーっ! youtube って you tube だったのか! なぜか最後の e が a に見えたらしく「ようつば」だとばっかり思ってた。
さらに言えば「よつばと!」と何か関係があるものだと思っていた。
(04:00)