[英]Elm Parser loop does not terminate
我遇到了一個我無法弄清楚的解析器遞歸問題。 任何關於導致問題的建議將不勝感激。
當使用有限數量的元素定義 function rawData
時,以下代碼工作正常(如下面的注釋代碼所示)。 但是當使用Parser.loop
定義時不會停止(直到堆棧溢出),如代碼中所示。 相同的循環結構適用於所有其他功能(例如files
和directories
)
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.