FParsec:如何组合解析器以便它们以任意顺序匹配

2024-04-30

任务是找到特定的键值对并解析它们。这些对可以按任何顺序出现。我的部分工作尝试:

open FParsec

type Parser<'a> = Parser<'a, unit>
type Status = Running | Done

type Job = 
    { Id: int
      Status: Status
      Count: int }

let ws = spaces

let jobId: Parser<int> = ws >>. skipStringCI "Job id" >>. ws >>. skipChar '=' >>. ws >>. pint32

let status: Parser<Status> = 
    ws >>. skipStringCI "Status" >>. ws >>. skipChar '=' >>. ws >>. (
        (skipStringCI "Running" >>% Running) <|> (skipStringCI "Done" >>% Done))

let count: Parser<int> = ws >>. skipStringCI "Count" >>. ws >>. skipChar '=' >>. ws >>. pint32

let parse: Parser<Job> = parse {
    do! skipCharsTillStringCI "Job id" false 1000
    let! id = jobId
    do! skipCharsTillStringCI "Status" false 1000
    let! status = status
    do! skipCharsTillStringCI "Count" false 1000
    let! count = count
    return { Id = id; Status = status; Count = count }}

[<EntryPoint>]
let main argv = 
    let sample = """
Some irrelevant text.
Job id = 33
Some other text.
Status = Done
And another text.
Count = 10
Trailing text.
"""
    printfn "%A" (run parse sample)
    0
(* 
result:
 Success: {Id = 33;
 Status = Done;
 Count = 10;} 
*)

所以,它可以工作,但有两个问题:明显的重复(jobId函数中的“Job id”和顶级解析器中的“Job id”等),并且它需要“Job id”,“Status”和“Count” “按照这个特定的顺序进行排序,这根据要求是错误的。

我有一种强烈的感觉,有一个优雅的解决方案可以解决这个问题。

Thanks!


第一个问题(重复)可以通过少量重构来解决。基本思想是将每个解析器包装到一个可以执行跳过的包装器中。
Note由于这段代码还远未达到完美,我只是尝试使重构尽可能小。

let jobId: Parser<int> = pint32

let status: Parser<Status> = 
    (skipStringCI "Running" >>% Running) <|> (skipStringCI "Done" >>% Done)

let count: Parser<int> = pint32

let skipAndParse prefix parser =
    skipCharsTillStringCI prefix false 1000
    >>. ws >>. skipStringCI prefix >>. ws >>. skipChar '=' >>. ws >>. parser

let parse: Parser<Job> = parse {
    let! id = skipAndParse "Job id" jobId
    let! status = skipAndParse "Status"  status
    let! count = skipAndParse "Count" count
    return { Id = id; Status = status; Count = count }}

第二个问题更复杂。如果您希望数据线以自由顺序出现,则必须考虑以下情况:

  • not all存在数据线;
  • 出现某条数据线twice或者更多;

为了缓解这种情况,您需要生成一个list找到的数据行,分析所需的一切是否都存在,并决定如何处理任何可能的重复项。

请注意,每个数据行不能再有“跳过”部分,因为它可能会在实际解析器之前跳过信息行。

let skipAndParse2 prefix parser =
    ws >>. skipStringCI prefix >>. ws >>. skipChar '=' >>. ws >>. parser

// Here, you create a DU that will say which data line was found
type Result =
    | Id of int
    | Status of Status
    | Count of int
    | Irrelevant of string

// here's a combinator parser
let parse2 =
    // list of possible data line parsers
    // Note they are intentionally reordered
    [
    skipAndParse2 "Count" count |>> Count
    skipAndParse2 "Status"  status |>> Status
    skipAndParse2 "Job id" jobId |>> Id
    // the trailing one would skip a line in case if it has not
    // been parsed by any of prior parsers
    // a guard rule is needed because of specifics of
    // restOfLine behavior at the end of input: namely, it would
    // succeed without consuming an input, which leads
    // to an infinite loop. Actually FParsec handles this and
    // raises an exception
    restOfLine true .>> notFollowedByEof |>> Irrelevant
    ]
    |> List.map attempt // each parser is optional
    |> choice // on each iteration, one of the parsers must succeed
    |> many // a loop

运行代码:

let sample = "
Some irrelevant text.\n\
Job id = 33\n\
Some other text.\n\
Status = Done\n\
And another text.\n\
Count = 10\n\
Trailing text.\n\
"

sample |> run parse2 |> printfn "%A "

将产生以下输出:

Success: [Irrelevant ""; Irrelevant "Some irrelevant text."; Id 33;
Irrelevant ""; Irrelevant "Some other text."; Status Done; Irrelevant "";
Irrelevant "And another text."; Count 10; Irrelevant ""]

进一步处理需要过滤Irrelevant元素,检查重复或缺失的项目,并形成Job记录或提出错误。


更新:进一步处理隐藏的简单示例Result并返回Job option反而:

// naive implementation of the record maker
// return Job option
// ignores duplicate fields (uses the first one)
// returns None if any field is missing
let MakeJob arguments =
    let a' =
        arguments
        |> List.filter (function |Irrelevant _ -> false | _ -> true)

    try
        let theId     = a' |> List.pick (function |Id x -> Some x | _ -> None)
        let theStatus = a' |> List.pick (function |Status x -> Some x | _ -> None)
        let theCount  = a' |> List.pick (function |Count x -> Some x | _ -> None)
        Some { Id=theId; Status = theStatus; Count = theCount }
    with
        | :?System.Collections.Generic.KeyNotFoundException -> None

要使用它,只需将以下行添加到代码中parse2:

|>> MakeJob
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FParsec:如何组合解析器以便它们以任意顺序匹配 的相关文章

  • 从 F# 调用 Newtonsoft.Json 出现意外结果

    我没有从该 F 代码中获得预期结果 我希望 t 包含调用 JsonSchema Parse json 的结果 但它是空的 我究竟做错了什么 open Newtonsoft Json open Newtonsoft Json Schema l
  • F# 中的数组初始化

    如何根据给定的记录类型在 F 中创建和初始化数组 假设我想创建一个包含 100 个 record1 记录的数组 e g type record1 value1 string value2 string let myArray Array i
  • 如何使 FSI 在 NET5 下工作并让愚蠢的 stackoverflow 消息“标题不能包含...”闭嘴?

    我正在将一个相当小的 F 项目从 Net Framework 迁移到 NET5 迁移非常简单 一切正常 包括测试 但是 当我运行一些脚本时 我现在收到以下错误 Microsoft R F Interactive version 11 0 0
  • 使用不区分大小写的比较从集合中减去记录

    我有一组记录 type Person Name string Age int let oldPeople set Name The Doctor Age 1500 Name Yoda Age 900 与上面的硬编码示例不同 这组数据实际上来
  • 像 Javascript 对象一样循环遍历 F# 记录

    在 javascript 中 我可以使用简单的 for 循环访问对象的每个属性 如下所示 var myObj x 1 y 2 var i sum 0 for i in myObj sum sum myObj i 我想知道我是否可以用 F 做
  • F# 中的选项类型如何工作

    因此 我一直在阅读 Apress 的 Expert F 书籍 主要将其用作构建玩具式 F 库时的参考 但有一点我未能掌握 那就是 Option 类型 它是如何工作的以及它在现实世界中的用途是什么 选项类型至少为similar to Null
  • 地图中的一组键

    我有一个地图 X 我试图获取一组满足特定条件的键 如下所示 Map Keys X gt Set filter fun x gt 但我找不到从 F 的 Map 集合中获取密钥的方法 转换你的map http msdn microsoft co
  • 如何使用 WebSharper 在服务器上生成 Google Visualizations 数据

    我的目标是能够在服务器上为 Google Visualizations 生成数据 然后将其作为 java 脚本传递给客户端 以便可以将其呈现为折线图 我下面的示例可以正确编译 但在浏览器中呈现时会产生错误 在服务器上构建 DataCommo
  • 使用 System.Text.Json 序列化记录成员

    我在记录中使用自我引用成员 如下所示 type Payload Id Guid member x DerivedProperty Derived Property using id x Id NewtonSoft Json会序列化这个 但是
  • 在构建过程中引用自身内部的记录

    我正在尝试创建一条记录 该记录在同一构造函数中使用先前定义的字段之一来计算另一个字段的值 例如 myRecordType Foo int Bar int myRecord Foo 5 Bar Array init Foo fun i gt
  • 将可区分的联合传递给 InlineData 属性

    我正在尝试对一个解析器进行单元测试 该解析器解析字符串并返回相应的抽象语法树 表示为可区分的联合 我认为使用 Xunit Extensions 属性会非常紧凑InlineData将所有测试用例堆叠在一起
  • 为什么 F# 的默认集合是排序的,而 C# 的不是?

    当从 C 世界迁移到 F 最惯用的可能 思维方式时 我发现了这个有趣的差异 在 C 的 OOP mutable 世界中 默认的集合集合似乎是HashSet https learn microsoft com en us dotnet api
  • 如何在 F# 中打印整个列表?

    当我使用 Console WriteLine 打印列表时 它默认仅显示前三个元素 如何让它打印列表的全部内容 您可以将 A 格式说明符与 printf 一起使用来获得 美化的 列表打印输出 但与对象上的 Console WriteLine
  • 如何在 F# 中定义这种惰性(无限?)数据结构

    我在定义以下简单文本光标时遇到问题 该光标由元组表示 其中第一个元素是当前字符 如果函数获取下一个元素或崩溃 则第二个元素是 let rec nextAt index text if index lt String length text
  • 如果目标是 x64,为什么 Seq.iter 比 for 循环快 2 倍?

    免责声明 这是微基准测试 如果您对此主题感到不满意 请不要评论诸如 过早优化是邪恶的 之类的言论 示例是针对 x64 Net4 5 Visual Studio 2012 F 3 0 的发行版 并在 Windows 7 x64 中运行 经过分
  • F#:模式构成?

    我正在尝试编写一个由另外两个模式组成的模式 但我不确定如何去做 我的输入是字符串列表 文档 我有一个与文档标题匹配的模式和一个与文档正文匹配的模式 该模式应该匹配整个文档并返回标题和正文模式的结果 您可以使用以下命令一起运行两个模式 您在问
  • F# 中的递归对象?

    这段 F 代码 let rec reformat new EventHandler fun gt b TextChanged RemoveHandler reformat b gt ScrollParser rewrite contents
  • 使用列表匹配绑定值(没有编译器警告)

    假设我有一个需要一些时间的函数int参数 但在其中我将使用float32 我不想使用float32 i无处不在的功能 相反 我想这样做 let x float32 x let y float32 y let w float32 w let
  • 如何在 F# 测量单位上定义扩展成员?

    暂且不说我们是否应该对像角度这样的无单位概念使用测量单位 假设我已经定义了degree and radianF 中的单位 type
  • 如何在 F# 中使用 LINQ 更新数据库中的表?

    我看过很多有关如何查询数据库的示例 但没有看到有关如何更新记录的示例 下面是我编写的用于检索表的简单代码 但有人可以解释一下如何修改字段 例如lastActiveDate 并更新数据库上的表 谢谢你 周日 open System open

随机推荐