【问题标题】:With FParsec, how does one use the manyCharsTill and between parsers and not fail on the closing string?使用 FParsec,如何在解析器之间使用 manyCharsTill 而不会在结束字符串上失败?
【发布时间】:2018-08-10 01:00:26
【问题描述】:

我正在尝试使用 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\"\"\""

解析器失败并出现以下错误:

失败:“Ln 中的错误:1 Col:8 “”“x”“” ^ 注意:错误发生在输入流的末尾。期望:'"""'

我对此感到困惑。 manyCharsTill multiLineStringContents (pstring "\"\"\"") 解析器不会停在""" 处,以便between 解析器找到它吗?为什么解析器会吃掉所有输入,然后 between 解析器会失败?

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

但我看不出那个解决方案与我在这里所做的有什么不同,真的。

【问题讨论】:

    标签: parsing f# fparsec


    【解决方案1】:

    manyCharsTill documentation 说(强调我的):

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

    所以您不想将betweenmanyCharsTill 结合使用;你想做pstring "\"\"\"" &gt;&gt;. manyCharsTill (pstring "\"\"\"")之类的事情。

    但是碰巧,我可以为您节省很多工作。我在业余时间一直在用 FParsec 开发一个 TOML 解析器。它还远未完成,但字符串部分可以正常工作并正确处理反斜杠转义(据我所知:我已经彻底测试但并非详尽无遗)。我唯一缺少的是“如果第一个换行符出现在开始分隔符之后,则去掉它”规则,您已经使用optional newline 处理了它。因此,只需将该位添加到我下面的代码中,您就应该有一个可以工作的 TOML 字符串解析器。

    顺便说一句,我计划在 MIT 许可下许可我的代码(如果我完成它)。因此,我特此在 MIT 许可下发布以下代码块。如果它对您有用,请随意在您的项目中使用它。

    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
    

    【讨论】:

    • 太棒了!是的,我发现了一个几年没有接触过的较老的开源东西,所以我分叉了它:github.com/aggieben/FPConfig。我开始重写其中的一些内容,因为我认为我开始使用的解析器......过于程序化。我正要发布自己的答案,意识到我明显的错误,但你打败了我,所以有一些虚假的互联网积分! :-)
    • 哦,很好。你开始做整数解析了吗?如果你还没有,我会向你发送一个 PR,其中包含我在整数上所做的工作。 (FParsec 有一个很好的 int 解析器,可以处理十六进制、八进制、二进制等,但它不像 TOML 那样禁止-0xff,所以我推出了自己的禁止负数的十六进制解析器)。实际上,预计接下来几天会有几个 PR:我会查看该项目并贡献我最近所做的任何尚未完成的工作。
    • 现有的 TOML 解析器据说是符合 v0.4 的,但我不知道那里的测试是否真的表明了这一点。我正在完全重写并瞄准 0.5,因为我认为我可以使解析器更简单。我目前在dev 分支上工作。
    • 什么是exactly?我在 FParsec v1.1.0 和你的 repo cloudRoutine/Toml.FSharp 中找不到它
    【解决方案2】:

    @rmunn 提供了正确答案,谢谢!在更多地使用 FParsec API 之后,我也以稍微不同的方式解决了这个问题。正如另一个答案中所解释的,manyCharTillendp 参数正在吃掉关闭的 """,所以我需要切换到不会这样做的东西。使用lookAhead 进行简单修改即可:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多