F# - 在运行时创建递归的判别联合类型

3

我正在尝试编写一个简单的解释器,使用F#控制海龟。
我创建了以下递归联合类型来表示海龟将接受的少数命令。代码将由“命令列表”表示,使用简单的递归函数执行不应该太难。

type Command = 
| Repeat of amount * Command list
| Forward of amount
| Turn of direction * amount

我希望将这个解释器的空格划分清晰,使得源代码看起来如下。
Forward 75
Repeat 4
    Forward 10
    Turn Right 50
    Repeat 6
        Forward 20
        Turn Right 60
        Repeat 8
            Forward 15
            Turn Left 30
    Turn Right 10
Forward 25

我有一个函数,根据缩进级别将所有内容分成int*字符串列表。所以Repeat 4变成(0, "Repeat 4"),Forward 10则变成(1, "Forward 10"),Turn Left 30则变成(3, "Turn Left 30")。

/// Creates indentation chart for generating a syntax tree by cycling 
/// through a list of strings generated
///     by string.Split and counting the empty strings before each non-empty 
//// string.
let indent strs = 
    let rec inner strs search cmds indent temp =
        match strs, search with
        | [], _ -> (indent,temp)::cmds
        | ""::tail, Cmd -> inner tail Indent ((indent,temp)::cmds) 0 "" // 
Newline started -> push cached counter and command string
        | ""::tail, Indent -> inner tail Indent cmds (indent+1) temp // Next 
level of indent -> increment indent counter
        | h::tail, Cmd -> inner tail Cmd cmds indent (temp + " " + h)
        | h::tail, Indent -> inner tail Cmd cmds indent (temp + h)
        | h::tail, Start -> inner tail Cmd cmds indent (temp + h)
    inner strs Start [] 0 "" |> List.rev

let splitIndent (text:string) = text.Trim().Split() |> Array.toList |> 
    indent

现在我遇到了困难。我有一系列带有缩进级别的命令列表,但我不知道如何动态创建递归式的联合类型。我知道如何硬编码它。它看起来像这样,

let tree = [
    Forward 75
    Repeat (4, [
        Forward 10
        Turn (Right, 50)
        Repeat (6, [
            Forward 20
            Turn (Right, 60)
            Repeat (8, [
                Forward 15
                Turn (Left, 30)
                ])])
        Turn (Right, 10)])
    Forward 25]

但我不知道如何根据不同的输入字符串生成类似这样的东西。
我阅读了许多与树、判别式联合和动态创建递归类型等相关的StackOverflow帖子,但没有找到符合我的需求的内容。我尝试修改我找到的少数示例,最接近的是Tomas Petricek在F# transform list to a tree中提供的答案。插入联合情况和模式匹配以使其正常工作使我更接近目标,但它省略了很多命令并重复了一些其他命令。
这是我迄今为止想出的最好方法,但它并不能得到所有的命令。
/// Takes the indent,command pairs list and produces a list of commands. 
/// Some of these are nested in a tree structure based on their indent.
let buildProgram (diagram:(int*string) list) : Command list =   
    let rec collect indx lis commands =
        match lis with
        | [] -> commands
        | x::xs ->
            match fst x with
            | i when i = indx -> 
                match split (snd x) with
                | "Forward"::NUM n::tail -> collect i xs commands@[Forward 
n]
                | "Turn"::ARG a::NUM n::tail -> collect i xs 
commands@[Turn(a,n)]
                | "Repeat"::NUM n::tail -> commands@([(Repeat (n, (collect 
(i+1) xs [])))] |> List.rev)
            | i when i < indx ->
                match split (snd x) with
                | "Forward"::NUM n::tail -> collect (i-1) xs 
commands@[Forward n]
                | "Turn"::ARG a::NUM n::tail -> collect (i-1) xs 
commands@[Turn(a,n)]
                | "Repeat"::NUM n::tail -> commands@([(Repeat (n, (collect 
(i-1) xs [])))] |> List.rev)
    collect 0 diagram [] |> List.rev

如何根据不同的输入在运行时创建递归判别联合?

如果您正在寻求有关代码的更具体帮助,您应该使用 最小化、完整化和可验证示例 更新问题。 - scrwtp
1个回答

1
你尝试做的是编写一个基于缩进的语法解析器,以产生类型为Command的值。您可以手动编写解析器,但一般建议使用解析器组合库,例如FParsec。 FParsec确实有陡峭的学习曲线,但它是在F#中编写解析器的“正确方法”。如果您决定使用它,您会发现本文特别有帮助- 使用FParsec解析基于缩进的语法

我一定会研究那个库。我正在做这个项目来了解编程语言,更具体地说是解释器的工作原理。FParsec的文档应该会很有帮助。问题仍然存在。我应该删除大部分上下文以获得更一般的回答吗? - Ryan156
@Ryan156:是的,如果你所说的更一般是指更具体(参见:MCVE)。这是“如何基于不同(字符串)输入在运行时创建递归鉴别联合”的一般答案。任何更一般的问题,最好放在SE/CS StackExchange上而不是这里。 - scrwtp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接