JavaScript版に対応させるHaskellでのParser実装
昨日のJavaScriptでの関数型パーザー( id:bellbind:20060529:1148843131 )を、きちんとする際、同等のHaskellコードと対応付けて説明を書こうと思った。
両方作るんだけど、型の問題とかあるので、先にHaskellでParserを実装してみた。
data PState result = PState { codeField :: String, posField :: Int, resultField :: result } deriving Show data Parser result = Parser { parseMethod :: String -> Int -> Maybe (PState result) } parserBind :: Parser a -> (a -> Parser b) -> Parser b parserBind src dst = Parser { parseMethod = newParse } where newParse inCode inPos = let srcParse = parseMethod src in case (srcParse inCode inPos) of Nothing -> Nothing Just newState -> let newCode = codeField newState in let newPos = posField newState in let newResult = resultField newState in let dstParser = dst newResult in let dstParse = parseMethod dstParser in dstParse newCode newPos parserNext :: Parser a -> Parser b -> Parser b parserNext src dst = Parser { parseMethod = newParse } where newParse inCode inPos = let srcParse = parseMethod src in case (srcParse inCode inPos) of Nothing -> Nothing Just newState -> let newCode = codeField newState in let newPos = posField newState in let newResult = resultField newState in let dstParse = parseMethod dst in dstParse newCode newPos parserReturn :: a -> Parser a parserReturn resultValue = Parser { parseMethod = newParse } where newParse inCode inPos = let resultState = PState { codeField = inCode, posField = inPos, resultField = resultValue } in Just resultState parserFail :: String -> Parser a parserFail error = Parser { parseMethod = newParse } where newParse inCode inPos = Nothing charParser :: Char -> Parser Char charParser ch = Parser { parseMethod = newParse } where newParse inCode inPos = if (inPos < (length inCode)) && ((inCode !! inPos) == ch) then let newState = PState { codeField = inCode, posField = inPos + 1, resultField = ch} in Just newState else Nothing manyParser :: Parser a -> Parser [a] manyParser parser = Parser { parseMethod = newParse } where newParse inCode inPos = parseNext [] inCode inPos parseNext results inCode inPos = case (parseMethod parser) inCode inPos of Nothing -> Just (PState { codeField = inCode, posField = inPos, resultField = results}) Just newState -> let newResults = results ++ [(resultField newState)] in let newCode = codeField newState in let newPos = posField newState in parseNext newResults newCode newPos orElseParser :: Parser a -> Parser a -> Parser a orElseParser parser1 parser2 = Parser { parseMethod = newParse } where newParse inCode inPos = let parser1Parse = parseMethod parser1 in case (parser1Parse inCode inPos) of Just newState -> Just newState Nothing -> let parser2Parse = parseMethod parser2 in parser2Parse inCode inPos instance Monad Parser where (>>=) src dst = parserBind src dst (>>) src dst = parserNext src dst return retVal = parserReturn retVal fail error = parserFail error stringParser [] = do return [] stringParser (ch:t) = do result <- charParser ch results <- stringParser t return (result:results) abcOrDefParser = manyParser (orElseParser (stringParser "abc") (stringParser "def")) parseCode parser code = (parseMethod parser) code 0
PStateはタプルを使えば簡単だけど、JavaScriptとの対応付けからレコード型にしてます。あとレコード型のメンバー名もStupid精神からメンバーアクセスとわかる名前にしてます。
このParserはMonadになっていて、これはきちんと動くコードです。ghciなどで読み込んで、
Main> parseCode abcOrDefParser "abcdefabcd"
などと打ちこんでやればいけます。
parseMethodの引数のあたりが少し変わってますが、なんとなく構造的には昨日のコードと似てるでしょう。