Общая проблема в том, что Parse c прекращает пробовать альтернативы, когда находит первый, который работает. Если вы напишите:
parseTest (atom <|> fact') "3!"
, это приведет к 3.0. Это потому, что парсер atom
успешно анализирует начальную часть строки "3"
, поэтому Parse c даже не пытается парсер fact'
. Оставшаяся строка "!"
остается для другого, более позднего парсера, для обработки.
В приведенном выше коде парсера, если вы попытаетесь выполнить синтаксический анализ:
parseTest addition "3!"
синтаксический анализатор addition
начинается с попытки парсера multiplication
. У синтаксического анализатора multiplication
есть строка:
lhv <- atom <|> fact' <|> negation'
, поэтому он начинает работу с парсера atom
. Этот синтаксический анализатор работает нормально и возвращает 3.0
, поэтому он никогда не утруждает себя попыткой fact'
или negation
.
Чтобы исправить свой синтаксический анализатор, необходимо убедиться, что вы не успешно проанализировали atom
в альтернативе, прежде чем пытаться fact'
. Вы можете начать с переключения порядка:
> parseTest (fact' <|> atom) "3!"
6.0
Это нормально работает для разбора "3!"
(и дает 6.0
), но не получается, если вы попытаетесь разобрать что-то еще:
> parseTest (fact' <|> atom) "3"
parse error at (line 1, column 2):
unexpected end of input
expecting "!"
Это связано с тем, что по соображениям эффективности Parse c автоматически не "возвращается". Если вы попытаетесь что-то проанализировать, и он «потребляет ввод» до того, как потерпит неудачу, он потерпит неудачу полностью, вместо того, чтобы попробовать больше альтернатив. Здесь fact'
начинается с вызова numberFact
, который успешно «потребляет» "3"
, а затем пытается char '!'
, что не удается. Таким образом, fact
«завершается неудачно после использования ввода», что приводит к немедленной ошибке разбора.
Вы можете переопределить это поведение, используя функцию try
:
> parseTest (try fact' <|> atom) "3"
3.0
> parseTest (try fact' <|> atom) "3!"
6.0
Здесь try fact'
применяет синтаксический анализатор fact'
, но обрабатывает "сбой после использования ввода", как если бы он был "сбой после использования ввода", поэтому можно проверить дополнительные альтернативы.
Если вы применили это изменение к обоим местам в вашем multiplication
парсер:
multiplication :: CalcParser Double
multiplication = do
spaces
lhv <- try fact' <|> atom <|> negation'
spaces
t <- many tail
return $ applyMany lhv t
where tail =
do
f <- star <|> div_
spaces
rhv <- try fact' <|> atom <|> negation'
spaces
return (`f` rhv)
и внес аналогичные изменения в ваш addition
парсер:
addition :: CalcParser Double
addition = do
spaces
lhv <- try fact' <|> multiplication <|> negation'
spaces
t <- many tail
return $ applyMany lhv t
where tail =
do
f <- plus <|> minus
spaces
rhv <- try fact' <|> multiplication <|> negation'
spaces
return (`f` rhv)
, тогда он будет работать лучше:
> parseTest addition "3!"
6.0
> parseTest addition "3"
3.0
> parseTest addition "3+2*6!"
1443.0
Это также Хорошая идея добавить анализатор eof
в ваш тест, чтобы убедиться, что у вас нет остаточного мусора, который не был проанализирован:
> parseTest addition "3 Hey, I could write anything here"
3.0
> parseTest (addition <* eof) "3 but adding eof will generate an error"
parse error at (line 1, column 4):
unexpected 'b'
expecting space, "*", "/", white space, "+", "-" or end of input