首页 文章

F# - 在运行时创建递归识别联盟

提问于
浏览
3

我正在尝试编写一个简单的解释器来使用F#控制Turtle .
我创建了以下递归联合类型来表示Turtle将接受的几个命令 . 代码将由"Command list"表示,使用简单的递归函数执行时不应该太难 .

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 *字符串列表 . 因此,重复4将变为(0,“重复4”),向前10将是(1,“向前10”),向左转30将是(3,“向左转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

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

1 回答

  • 1

    你要做的是为你的基于缩进的语法编写一个解析器,它将产生 Command 类型的值 .

    你当然可以手动滚动,但一般的建议是使用解析器组合库,如FParsec . FParsec确实有一个陡峭的学习曲线,但它是"the way to go"用于在F#中编写解析器 .

    如果你决定使用它,你会发现这篇文章特别有用 - Parsing indentation based syntax with FParsec .

相关问题