使用 FParsec 解析可能格式错误的输入

Using FParsec to parse possibly malformed input

我正在使用 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 '}'

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

let pdimObjs = many pdimObj

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

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

您正试图消耗太多 spaces。

看:pdimObj是一个pdim,后面跟着一些space,接着是pobj,它本身就是一个pnum后面跟着一些space秒。因此,如果您查看输入的第一部分:

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

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

解决这个问题的最小方法是让 pdimObjs 使用 many 而不是 sepBy:因为 pobj 已经消耗尾随的 space,所以不需要在 sepBy:

中消耗它们
let pdimObjs = many pdimObj

但在我看来,更简洁的方法是从 pobj 中删除 ws,因为从直觉上讲,尾随的 space 不是代表您的数字的一部分对象(无论是什么),而是通过 sepEndBy:

处理 pdimObjs 中可能的尾随 spaces
let pobj = pnum |>> fun x ->
    let on: ObjektNummer = int x.String
    on
...
let pdimObjs = sepEndBy pdimObj spaces1