使用 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 '}'
这里的主要问题在pdimObjs
。 sepBy
解析器失败,因为每个数字后面的分隔符空间已被 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
我正在使用 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 '}'
这里的主要问题在pdimObjs
。 sepBy
解析器失败,因为每个数字后面的分隔符空间已被 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