【问题标题】:Using FParsec to parse possibly malformed input使用 FParsec 解析可能格式错误的输入
【发布时间】:2021-05-18 06:09:24
【问题描述】:

我正在编写一个特定文件格式的解析器,使用 FParsec 作为学习 fsharp 的第一步。部分文件格式如下

{ 123 456 789 333 }

括号中的数字是成对的值,可以有任意数量的空格来分隔它们。所以这些也是要解析的有效内容:

{  22 456              7 333     }

当然,括号的内容可能是空的,即{}

此外,我希望解析器能够处理内容格式不正确的情况,例如。 { some descriptive text } 或者更可能是 { 12 3 4}(无效,因为 4 不会与任何东西配对)。在这种情况下,我只想将保存的内容单独处理。

到目前为止我有这个:

type DimNummer = int
type ObjektNummer = int
type DimObjektPair = DimNummer * ObjektNummer
type ObjektListResult = Result<DimObjektPair list, string>


let sieObjektLista = 
    let pnum = numberLiteral NumberLiteralOptions.None "dimOrObj"
    let ws = spaces
    let pobj = pnum .>> ws |>> fun x -> 
        let on: ObjektNummer = int x.String
        on
    let pdim = pnum |>> fun x -> 
        let dim: DimNummer = int x.String
        dim

    let pdimObj = (pdim .>> spaces1) .>>. pobj |>> DimObjektPair

    let toObjektLista(objList:list<DimObjektPair>) = 
        let res: ObjektListResult = Result.Ok objList
        res

    let pdimObjs = sepBy pdimObj spaces1
    let validList = pdimObjs |>> toObjektLista

    let toInvalid(str:string) = 
        let res: ObjektListResult = 
            match str.Trim(' ')  with 
            | "" -> Result.Ok []
            | _ -> Result.Error str
        res

    let invalidList = manyChars anyChar |>> toInvalid
    let pres = between (pchar '{') (pchar '}') (ws >>. (validList <|> invalidList) .>> ws)
    pres

let parseSieObjektLista = run sieObjektLista

但是在一个有效的样本上运行这个我得到一个错误:

{ 53735        7785  86231   36732         }
                     ^
Expecting: whitespace or '}'

【问题讨论】:

  • 只是一个提示:至少对于调试(但一般来说,我的口味)我会从 sieObjektLista 中取出所有嵌套函数,这样您就可以轻松地单独测试每个(子)解析器函数。您在 f# 交互式中调试它。一般来说,嵌套函数可以避免污染命名空间,但会使代码维护变得更加困难(不能再单独运行它们),所以通常最好避免使用它们,除非有充分的理由。
  • 感谢您的建议。我想保持模块界面相当干净,但我认为你是对的。如果所有解析器函数都可以访问,即使它们只是在内部使用,调试等也会变得更容易。

标签: parsing f# fparsec


【解决方案1】:

您试图占用太多空间。

看:pdimObj 是一个pdim,后跟一些空格,然后是pobj,它本身就是一个pnum,后跟一些空格。因此,如果您查看输入的第一部分:

{ 53735        7785  86231   36732         }
  \___/\______/\__/\/
    ^      ^    ^   ^
    |      |    |   |
   pnum    |    |   |
    ^   spaces1 |   |
    |           |   ws
   pdim        pnum  ^
     ^          ^    |
     |          \    /
     |           \  /
     |            \/
      \          pobj
       \          /
        \________/
            ^
            |
          pdimObj

从这里可以清楚地看到pdimObj 消耗了直到86231 的所有内容,包括它之前的空间。因此,当sepBypdimObjs 中查找下一个分隔符(即spaces1)时,它找不到任何分隔符。所以它失败了。

解决此问题的最小方法是让pdimObjs 使用many 而不是sepBy:因为pobj 已经使用了尾随空格,因此无需在sepBy 中也使用它们:

let pdimObjs = many pdimObj

但在我看来,更简洁的方法是从 pobj 中删除 ws,因为直观地说,尾随空格不是代表您的对象的数字的一部分(无论是什么),而是处理可能的pdimObjs 中的尾随空格通过 sepEndBy:

let pobj = pnum |>> fun x ->
    let on: ObjektNummer = int x.String
    on
...
let pdimObjs = sepEndBy pdimObj spaces1

【讨论】:

  • 这对于有效输入非常有用。如果 validList 失败,有关如何使 (validList &lt;|&gt; invalidList) 使用 invalidList 的任何建议?目前似乎期待dimOrObj}。但我希望它基本上使用 invalidList 解析器函数作为后备。
  • 设法通过使用attempt 和其他一些小调整来解决这个问题。
【解决方案2】:

这里的主要问题在于pdimObjssepBy 解析器失败,因为每个数字后面的分隔符空格已被 pobj 占用,因此 spaces1 无法成功。相反,我建议你试试这个:

let pdimObjs = many pdimObj

在您的测试输入中给出以下结果:

Success: Ok [(53735, 7785); (86231, 36732)]

【讨论】:

  • 不需要spaces,它们已经在pobj中消费了。
  • 好点。 FParsec 中的空白管理可能很棘手。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-05-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多