애플리카티브 스타일의 파서 구성
Parsec 라이브러리에서 제공하는 Applicative 인터페이스를 사용하여 파서를 조합하는 방법을 설명합니다. 주요 연산자로는 <$>, <*>, <$, <*, *>, <|>, many 등이 있습니다.
예제 1: 실수 형식 파싱
import Control.Applicative hiding ((<|>))
import Text.Parsec
-- 정수 부분 파서
integerPart :: Parsec String () String
integerPart = many1 digit
-- 양수 형태 (+숫자)
positive :: Parsec String () String
positive = char '+' >* integerPart
-- 음수 형태 (-숫자)
negative :: Parsec String () String
negative = (:) <$> char '-' <*> integerPart
-- 전체 정수 또는 부호 포함 숫자
integer :: Parsec String () String
integer = positive <|> negative <|> integerPart
-- 소수점 이후 부분 (선택 사항)
fractionalPart :: Parsec String () String
fractionalPart = option "" $ (:) <$> char '.' <*> integerPart
-- 실수 변환 함수
toFloat :: String -> Float
toFloat = read
-- 실수 파서: 정수 + 소수점 부분 합치기
floatParser :: Parsec String () Float
floatParser = fmap toFloat $ (++) <$> integer <*> fractionalPart
main :: IO ()
main = forever $ do
putStrLn "실수 입력: "
input <- getLine
parseTest floatParser input
실행 결과 예시:
> main
실수 입력:
2.3
2.3
실수 입력:
1
1.0
실수 입력:
-1
-1.0
실수 입력:
+6.98
6.98
char '+' >* integerPart: '+'를 먼저 일치시키고, 그 후에 정수를 파싱하지만 '+'의 결과는 무시하고 정수만 반환.(:) <$> char '-' <*> integerPart: '-'와 정수를 순차적으로 파싱한 후, 두 값을 결합하여 문자열로 만듦.(++) <$> integer <*> fractionalPart: 정수 부분과 소수 부분을 연결해 전체 문자열로 구성.option "" p: 파서p가 실패하면 빈 문자열을 반환.
예제 2: CSV 파일 파싱
import Text.Parsec
import Control.Applicative ((<$), (<*), (*>), liftA)
import Data.Char (chr)
-- CSV 파서
parseCSV :: String -> Either ParseError [[String]]
parseCSV = parse csvp ""
-- 메인 파서: 줄 단위로 분할 후 끝에 도달해야 함
csvp :: Parsec String () [[String]]
csvp = line `endBy` newline <* eof
-- 한 줄 파서: 콤마로 구분된 셀들
line :: Parsec String () [String]
line = cell `sepBy1` char ','
-- 셀 파서: 따옴표 안의 내용이나 일반 텍스트
cell :: Parsec String () String
cell = quotedCell <|> many (noneOf ",\n")
where
-- 따옴표로 둘러싸인 셀
quotedCell = between (char '"') (char '"') $
many $ noneOf "\"" <|> try ('"' <$ string "\"\"")
-- 내부에 이중 따옴표가 있는 경우 처리
doubleQuote = string "\"\"" <* return '"'
테스트 결과:
> parseCSV "a,b,c\n"
Right [["a","b","c"]]
> parseCSV "a,b,\"c\"\n"
Right [["a","b","c"]]
> parseCSV "a,b,\"c\"\"d\"\n"
Right [["a","b","c\"d"]]
line `endBy` newline <* eof: 줄 단위로 파싱한 후, 파일 끝까지 도달해야 성공.eof는 결과에 영향을 주지 않음.sepBy1 p sep: 하나 이상의p항목이sep로 구분되는 패턴을 매칭.char '"' <|> try ('"' <$ string "\"\""): 따옴표가 이어져 있으면 이를 하나의 문자로 처리.