2014-06-08 87 views
5

我想分析輸入的字符串這樣引號的字符串:"this is \"test \" message \"sample\" text"解析器使用秒差距

現在,我寫了一個解析器解析各個文本不帶任何引號:

parseString :: Parser String 
parseString = do 
    char '"' 
    x <- (many $ noneOf "\"") 
    char '"' 
    return x 

這解析簡單的字符串這樣:"test message"

然後我寫了一個解析器引號的字符串:

quotedString :: Parser String 
quotedString = do 
    initial <- string "\\\"" 
    x <- many $ noneOf "\\\"" 
    end <- string "\\\"" 
    return $ initial ++ x ++ end 

這個字符串的解析器是這樣的:\"test message\"

有沒有一種方法可以將兩個解析器組合起來,以便獲得所需的目標?解決這個問題的方法究竟是什麼?

+0

爲什麼你想剝去最初和最後的引號,但保留逃避反斜槓完好?我想你會想把輸入''\「ab \\\」c \「」'解析爲'「\」ab \\\「c \」「'(嚴格解析驗證)或者' 「ab \」c「',但似乎你想''ab \\\」c「',這似乎不是很明顯有用。 – dfeuer

+0

@dfeuer沒有特別的理由,只是玩弄Parsec。 – Sibi

回答

17

這是我會做什麼:

escape :: Parser String 
escape = do 
    d <- char '\\' 
    c <- oneOf "\\\"0nrvtbf" -- all the characters which can be escaped 
    return [d, c] 

nonEscape :: Parser Char 
nonEscape = noneOf "\\\"\0\n\r\v\t\b\f" 

character :: Parser String 
character = fmap return nonEscape <|> escape 

parseString :: Parser String 
parseString = do 
    char '"' 
    strings <- many character 
    char '"' 
    return $ concat strings 

現在,所有你需要做的是把它叫做:

parse parseString "test" "\"this is \\\"test \\\" message \\\"sample\\\" text\"" 

解析器組合是有點困難,首先要了解,但一旦你它比編寫BNF語法更容易。

+1

不應該'nonEscape'只是'noneOf'\\\「」',允許特殊字符字面上出現,而可能加速處理相當大? – dfeuer

+0

@dfeuer我想他只是包含一些額外的字符來演示它,以防我想添加它們。 :) – Sibi

+0

@Sibi,我的觀點是,將額外的轉義放入'escape' *的定義可能會更好,而不會將它們從'nonEscape'中排除。只有*必須被'nonEscape'排除的東西是''''''和''\\'' – dfeuer

2
quotedString = do 
    char '"' 
    x <- many (noneOf "\"" <|> (char '\\' >> char '\"')) 
    char '"' 
    return x 

我相信,這應該工作。

+0

這將在結果中包含\。 ''「\」「將被解析爲''''而不是''' –

+0

@Banthar,這似乎是OP的意圖。然而,代碼似乎沒有像Aadit M. Shah的答案那樣靈活,它看起來很難將其擴展爲支持逃避反斜槓。 – dfeuer

0

我寧願以下,因爲它更容易閱讀:

quotedString :: Parser String 
quotedString = do 
    a <- string "\"" 
    b <- concat <$> many quotedChar 
    c <- string "\"" 
    -- return (a ++ b ++ c) -- if you want to preserve the quotes 
    return b 
    where quotedChar = try (string "\\\\") 
        <|> try (string "\\\"") 
        <|> ((noneOf "\"\n") >>= \x -> return [x]) 

Aadit的解決方案可能會更快,因爲它不使用try但它可能難以閱讀。

請注意,它與Aadit的解決方案不同。我的解決方案忽略字符串中的轉義事物,並且真的只關心\"\\

例如,假設在字符串中有一個製表符。 我的解決方案成功解析了"\"\t\""Right "\t"。 Aadit的解決方案說unexpected "\t"expecting "\\" or "\""

另請注意,Aadit的解決方案只接受'有效'轉義。例如,它拒絕"\"\\a\""\a不是有效的轉義序列(根據man ascii,它代表系統鍾並且是有效的)。我的解決方案只返回Right "\\a"

所以我們有兩種不同的用例。

  • 我的解決方案:解析引用的字符串與可能轉義引號和逃脫逃脫

  • Aadit的解決方案:解析與有效的轉義序列引用的字符串,其中有效的逃生手段"\\\"\0\n\r\v\t\b\f"

0

我想解析引用的字符串並刪除解析步驟中用於轉義的所有反斜槓。用我的簡單語言,唯一可以逃脫的字符是雙引號和反斜槓。這裏是我的解決方案:

quotedString = do 
    string <- between (char '"') (char '"') (many quotedStringChar) 
    return string 
    where 
    quotedStringChar = escapedChar <|> normalChar 
    escapedChar = (char '\\') *> (oneOf ['\\', '"']) 
    normalChar = noneOf "\"" 
0

如果有人正在尋找一種更開箱解決方案,this answer in code-review也是這樣做的。這裏有一個完整的例子,用正確的進口:

import   Text.Parsec 
import   Text.Parsec.Language 
import   Text.Parsec.Token 

lexer :: GenTokenParser String u Identity 
lexer = makeTokenParser haskellDef 

strParser :: Parser String 
strParser = stringLiteral lexer 

parseString :: String -> Either ParseError String 
parseString = parse strParser ""