Reputation: 1382
I have a parser for double-quoted strings that mostly works fine, but when the end quote is missing it loops forever and crashes the app.
It is part of a web app written in Elm, and uses elm/parser.
It is based on an example from the Elm Github.
Here is a minimal example (run it in Ellie; change toParse to "\"" and it crashes the tab).
module Main exposing (main)
import Html
import Parser as P exposing ((|.), (|=))
import Debug
stringP : P.Parser String
stringP =
P.succeed identity
|. P.token "\""
|= P.loop [] stringHelp
stringHelp : List String -> P.Parser (P.Step (List String) String)
stringHelp revChunks =
P.oneOf
[ P.token "\""
|> P.map (\_ -> P.Done (String.join "" (List.reverse revChunks)))
, P.chompWhile isUninteresting
|> P.getChompedString
|> P.map (\chunk -> P.Loop (chunk :: revChunks))
]
isUninteresting : Char -> Bool
isUninteresting char =
char /= '\\' && char /= '"'
toParse =
"\""
main =
Html.text <| Debug.toString <| P.run stringP toParse
I can see what's wrong in a way - the chompWhile
bit succeeds even if the end of input is reached. I need something like this, but can't quite work out how to do it in this case.
Upvotes: 4
Views: 228
Reputation: 22497
Here's an alternative solution that checks for the end
token if a string terminates prematurely (Ellie):
module Main exposing (main)
import Html
import Parser as P exposing ((|.), (|=))
import Debug
stringP : P.Parser String
stringP =
( P.succeed identity
|. P.token "\""
|= P.loop [] stringHelp
) |> P.andThen
( \res ->
case res of
Ok str -> P.succeed str
Err msg -> P.problem msg
)
stringHelp : List String -> P.Parser (P.Step (List String) (Result String String))
stringHelp revChunks =
P.oneOf
[ P.end
|> P.map (\_ -> P.Done (Err "string ended prematurely"))
, P.token "\""
|> P.map (\_ -> P.Done (Ok (String.join "" (List.reverse revChunks))))
, P.chompWhile isUninteresting
|> P.getChompedString
|> P.map (\chunk -> P.Loop (chunk :: revChunks))
]
isUninteresting : Char -> Bool
isUninteresting char =
char /= '\\' && char /= '"'
toParse =
"\"hi\""
main =
Html.text <| Debug.toString <| P.run stringP toParse
Upvotes: 0
Reputation: 1382
I have found that a solution is to track the parsing position, and quit the loop if the position has not increased. It's not very pretty though; maybe someone will come up with something better.
The code is as the example in the question, but with these changes (new Ellie):
stringP : P.Parser String
stringP =
P.succeed identity
|. P.token "\""
|= P.loop ([], 0) stringHelp
|. P.token "\""
stringHelp : (List String, Int) -> P.Parser (P.Step (List String, Int) String)
stringHelp (revChunks, offset) =
P.succeed (stepHelp offset)
|= stringHelp2 revChunks
|= P.getOffset
stepHelp : Int -> (P.Step (List String) String) -> Int -> P.Step (List String, Int) String
stepHelp oldOffset step newOffset =
case step of
P.Done str ->
P.Done str
P.Loop revChunks ->
if newOffset > oldOffset then
P.Loop (revChunks, newOffset)
else
P.Done <| String.join "" <| List.reverse revChunks
stringHelp2 : List String -> P.Parser (P.Step (List String) String)
stringHelp2 revChunks =
P.chompWhile isUninteresting
|> P.getChompedString
|> P.map (\chunk -> P.Loop (chunk :: revChunks))
This ends with the desired parser error that there is a missing quote.
Upvotes: 3