代码之家  ›  专栏  ›  技术社区  ›  Ben Collins

对于FParsec,如何使用manyCharsTill和解析器之间的函数,并且在结束字符串上不失败?

  •  0
  • Ben Collins  · 技术社区  · 6 年前

    我正试图使用FParsec来解析一个TOML多行字符串,但是我在使用结束分隔符时遇到了问题( """ ). 我有以下解析器:

    let controlChars = 
        ['\u0000'; '\u0001'; '\u0002'; '\u0003'; '\u0004'; '\u0005'; '\u0006'; '\u0007';
         '\u0008'; '\u0009'; '\u000a'; '\u000b'; '\u000c'; '\u000d'; '\u000e'; '\u000f';
         '\u0010'; '\u0011'; '\u0012'; '\u0013'; '\u0014'; '\u0015'; '\u0016'; '\u0017';
         '\u0018'; '\u0019'; '\u001a'; '\u001b'; '\u001c'; '\u001d'; '\u001e'; '\u001f';
         '\u007f']
    
    let nonSpaceCtrlChars =
        Set.difference (Set.ofList controlChars) (Set.ofList ['\n';'\r';'\t'])
    
    let multiLineStringContents : Parser<char,unit> =
        satisfy (isNoneOf nonSpaceCtrlChars)
    
    let multiLineString         : Parser<string,unit> =
        optional newline >>. manyCharsTill multiLineStringContents (pstring "\"\"\"")
        |> between (pstring "\"\"\"") (pstring "\"\"\"") 
    
    let test parser str =
        match run parser str with
        | Success (s1, s2, s3) -> printfn "Ok: %A %A %A" s1 s2 s3
        | Failure (f1, f2, f3) -> printfn "Fail: %A %A %A" f1 f2 f3
    

    当我测试时 multiLineString 针对这样的输入:

    test multiLineString "\"\"\"x\"\"\""
    

    分析程序失败,出现以下错误:

    失败:“行:1列:8”“x”“中的错误” ^注意:错误发生在输入流的末尾。应为:''''

    我被这个弄糊涂了。不会的 manyCharsTill multiLineStringContents (pstring "\"\"\"") 分析器停止于 """ 对于 between 找到它的分析器?为什么解析器会吃掉所有的输入,然后失败 之间 分析器?

    这似乎是一个相关的帖子: How to parse comments with FParsec

    但我真的不明白解决这个问题的方法和我在这里所做的有什么不同。

    2 回复  |  直到 6 年前
        1
  •  2
  •   rmunn    6 年前

    这个 manyCharsTill documentation 说(强调我的):

    manyCharsTill cp endp 使用字符分析器分析字符 cp 直到解析器 endp 成功。它停止了 之后 尾端 并将解析的字符作为字符串返回。

    所以你不想用 between 结合 许多人 ;你想做一些 pstring "\"\"\"" >>. manyCharsTill (pstring "\"\"\"") .

    但碰巧,我可以帮你省下很多工作。业余时间我一直在用FParsec开发一个TOML解析器。它还远未完成,但字符串部分可以正常工作并正确处理反斜杠转义(据我所知:我已经进行了彻底的测试,但还没有完全测试)。我唯一缺少的是“strip first newline if it appear right after The opening delimiter”规则,您已经用它处理过了 optional newline . 所以只要在下面的代码中添加这个位,就应该有一个工作的TOML字符串解析器。

    顺便说一句,我计划在麻省理工学院的许可下授权我的代码(如果我完成了它)。所以我在麻省理工学院的许可下发布了下面的代码块。如果对你有用的话,可以在你的项目中使用它。

    let pShortCodepointInHex = // Anything from 0000 to FFFF, *except* the range D800-DFFF
        (anyOf "dD" >>. (anyOf "01234567" <?> "a Unicode scalar value (range D800-DFFF not allowed)") .>>. exactly 2 isHex |>> fun (c,s) -> sprintf "d%c%s" c s)
        <|> (exactly 4 isHex <?> "a Unicode scalar value")
    
    let pLongCodepointInHex = // Anything from 00000000 to 0010FFFF, *except* the range D800-DFFF
            (pstring "0000" >>. pShortCodepointInHex)
            <|> (pstring "000"  >>. exactly 5 isHex)
            <|> (pstring "0010" >>. exactly 4 isHex |>> fun s -> "0010" + s)
            <?> "a Unicode scalar value (i.e., in range 00000000 to 0010FFFF)"
    
    let toCharOrSurrogatePair p =
        p |> withSkippedString (fun codePoint _ -> System.Int32.Parse(codePoint, System.Globalization.NumberStyles.HexNumber) |> System.Char.ConvertFromUtf32)
    
    let pStandardBackslashEscape =
        anyOf "\\\"bfnrt"
        |>> function
            | 'b' -> "\b"      // U+0008 BACKSPACE
            | 'f' -> "\u000c"  // U+000C FORM FEED
            | 'n' -> "\n"      // U+000A LINE FEED
            | 'r' -> "\r"      // U+000D CARRIAGE RETURN
            | 't' -> "\t"      // U+0009 CHARACTER TABULATION a.k.a. Tab or Horizonal Tab
            | c   -> string c
    
    let pUnicodeEscape =     (pchar 'u' >>. (pShortCodepointInHex |> toCharOrSurrogatePair))
                         <|> (pchar 'U' >>. ( pLongCodepointInHex |> toCharOrSurrogatePair))
    
    let pEscapedChar = pstring "\\" >>. (pStandardBackslashEscape <|> pUnicodeEscape)
    
    let quote = pchar '"'
    let isBasicStrChar c = c <> '\\' && c <> '"' && c > '\u001f' && c <> '\u007f'
    let pBasicStrChars = manySatisfy isBasicStrChar
    let pBasicStr = stringsSepBy pBasicStrChars pEscapedChar |> between quote quote
    
    let pEscapedNewline = skipChar '\\' .>> skipNewline .>> spaces
    let isMultilineStrChar c = c = '\n' || isBasicStrChar c
    let pMultilineStrChars = manySatisfy isMultilineStrChar
    
    
    let pTripleQuote = pstring "\"\"\""
    
    let pMultilineStr = stringsSepBy pMultilineStrChars (pEscapedChar <|> (notFollowedByString "\"\"\"" >>. pstring "\"")) |> between pTripleQuote pTripleQuote
    
        2
  •  1
  •   Ben Collins    6 年前

    @rmunn给出了正确的答案,谢谢!在进一步使用FParsec API之后,我还以稍微不同的方式解决了这个问题。正如另一个答案所解释的那样 endp 论证 manyCharTill 在吃闭门羹 """ ,所以我需要换一个不会那样做的东西。简单的修改使用 lookAhead 做了个诡计:

    let multiLineString         : Parser<string,unit> =
        optional newline >>. manyCharsTill multiLineStringContents (lookAhead (pstring "\"\"\""))
        |> between (pstring "\"\"\"") (pstring "\"\"\"")