繁体   English   中英

Elm 解析器循环不会终止

[英]Elm Parser loop does not terminate

我遇到了一个我无法弄清楚的解析器递归问题。 任何关于导致问题的建议将不胜感激。

当使用有限数量的元素定义 function rawData时,以下代码工作正常(如下面的注释代码所示)。 但是当使用Parser.loop定义时不会停止(直到堆栈溢出),如代码中所示。 相同的循环结构适用于所有其他功能(例如filesdirectories

module Reader exposing (..)

import Parser exposing (..)

type TermCmd
    = CD Argument
    | LS


type Argument
    = Home
    | UpOne
    | DownOne String


type Content
    = Dir String (List Content)
    | File Int String String


type alias RawData =
    List ( List TermCmd, List Content )


rawData : Parser RawData
rawData =
    loop [] <| loopHelper dataChunk        -- This never ends...

-- succeed (\a b c d -> [ a, b, c, d ])    -- but this works
--     |= dataChunk
--     |= dataChunk
--     |= dataChunk
--     |= dataChunk


dataChunk : Parser ( List TermCmd, List Content )
dataChunk =
    succeed (\cmds ctnt -> ( cmds, ctnt ))
        |= commands
        |= contents


directory : Parser Content
directory =
    succeed Dir
        |. symbol "dir"
        |. spaces
        |= (chompUntilEndOr "\n"
                |> getChompedString
           )
        |= succeed []
        |. spaces


file : Parser Content
file =
    succeed File
        |= int
        |. spaces
        |= (chompWhile (\c -> c /= '.' && c /= '\n')
                |> getChompedString
           )
        |= (chompUntilEndOr "\n"
                |> getChompedString
                |> Parser.map (String.dropLeft 1)
           )
        |. spaces


command : Parser TermCmd
command =
    succeed identity
        |. symbol "$"
        |. spaces
        |= oneOf
            [ succeed CD
                |. symbol "cd"
                |. spaces
                |= argument
            , succeed LS
                |. symbol "ls"
            ]
        |. spaces


argument : Parser Argument
argument =
    oneOf
        [ succeed UpOne |. symbol ".."
        , succeed Home |. symbol "/"
        , succeed DownOne |= (chompUntilEndOr "\n" |> getChompedString)
        , problem "Bad argument"
        ]
        |. spaces

contents : Parser (List Content)
contents =
    let
        contentHelper revContent =
            oneOf
                [ succeed (\ctnt -> Loop (ctnt :: revContent))
                    |= file
                , succeed (\ctnt -> Loop (ctnt :: revContent))
                    |= directory
                , succeed ()
                    |> map (\_ -> Done (List.reverse revContent))
                ]
    in
    loop [] contentHelper


commands : Parser (List TermCmd)
commands =
    loop [] <| loopHelper command


directories : Parser (List Content)
directories =
    loop [] <| loopHelper directory


files : Parser (List Content)
files =
    loop [] <| loopHelper file


loopHelper : Parser a -> List a -> Parser (Step (List a) (List a))
loopHelper parser revContent =
    oneOf
        [ succeed (\ctnt -> Loop (ctnt :: revContent))
            |= parser
        , succeed ()
            |> map (\_ -> Done (List.reverse revContent))
        ]
sampleInput =
    "$ cd /\n$ ls\ndir a\n14848514 b.txt\n8504156 c.dat\ndir d\n$ cd a\n$ ls\ndir e\n29116 f\n2557 g\n62596 h.lst\n$ cd e\n$ ls\n584 i\n$ cd ..\n$ cd ..\n$ cd d\n$ ls\n4060174 j\n8033020 d.log\n5626152 d.ext\n7214296 k"

rawData function 进入无限循环,但相同的构造( loop [] <| loopHelper parser )在其他任何地方都可以正常工作。

通过在空字符串上运行四步解析器(即开始succeed (\ab c d -> [ a, b, c, d ]) ),您可能会得到问题所在的提示。 如果这样做,您会得到以下结果:

Ok [([],[]),([],[]),([],[]),([],[])]

花点时间考虑一下五步解析器、十步解析器甚至 100 步解析器会得到什么。 loop提供了一个可以运行任意数量步骤的解析器。

loop function 的 Elm 文档提示了您的问题:

succeed ()chompWhile Char.isAlpha可以在不消耗任何字符的情况下成功。 所以在某些情况下,您可能希望使用getOffset来确保每个步骤实际消耗的字符。 否则你可能会陷入无限循环!

您的解析器遇到无限循环,因为它正在输出一个无限长的元组列表,每个元组都有一个空的命令列表。 您的解析器在生成每个这样的元组时不消耗任何字符,因此它将永远循环。

在您的情况下,空的命令列表似乎没有意义。 所以我们必须确保一个空的命令列表会导致解析失败。

一种方法是编写一个loopHelper的变体,如果列表为空,它就会失败:

checkNonEmpty : List a -> Parser ()
checkNonEmpty list =
    if List.isEmpty list then
        problem "List is empty"

    else
        succeed ()


loopHelperNonEmpty : Parser a -> List a -> Parser (Step (List a) (List a))
loopHelperNonEmpty parser revContent =
    oneOf
        [ succeed (\ctnt -> Loop (ctnt :: revContent))
            |= parser
        , checkNonEmpty revContent
            |> map (\_ -> Done (List.reverse revContent))
        ]

(我找不到在这里介绍getOffset的简单方法,所以我做了一些不同的事情。)

然后,您更改commands的定义以使用此 function 而不是loopHelper

commands : Parser (List TermCmd)
commands =
    loop [] <| loopHelperNonEmpty command

我对您的代码进行了此更改,它生成了以下 output:

Ok
    [ ( [ CD Home, LS ]
        , [ Dir "a" [], File 14848514 "b" "txt", File 8504156 "c" "dat", Dir "d" [] ]
        )
    , ( [ CD (DownOne "a"), LS ]
        , [ Dir "e" [], File 29116 "f" "", File 2557 "g" "", File 62596 "h" "lst" ]
        )
    , ( [ CD (DownOne "e"), LS ]
        , [ File 584 "i" "" ]
        )
    , ( [ CD UpOne, CD UpOne, CD (DownOne "d"), LS ]
        , [ File 4060174 "j" "", File 8033020 "d" "log", File 5626152 "d" "ext", File 7214296 "k" "" ]
        )
    ]

(为了清楚起见,我已经对其进行了格式化。在调查您的代码时,我只是使用Debug.toString()将解析器的结果输出到浏览器 window 中,但这会变成一长行。我将其粘贴到 VS Code 中,添加一些换行符并使用 elm-format 将其格式化为更好的格式。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM