簡體   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