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)
Copyright (c) 2002-2007 青木峰郎 / Minero Aoki. All rights reserved.