Как реализовать функцию парсера с плавающей точкой в ​​Haskell коде для целочисленной обработки? - PullRequest
0 голосов
/ 15 января 2020

Обращаясь к описанию моего задания (я студент с базовым c опытом Haskell), я должен сделать простой анализатор калькулятора с использованием Text.Parse c. До сих пор эта программа могла читать некоторые строковые данные для выполнения анализа только через целочисленные значения, например:

parseTest addition "5 + 8 / 4"

Существует полный код программы, который я на самом деле:

import Text.Parsec hiding(digit)
import Data.Functor

type CalcParser a = Parsec String () a

digit :: CalcParser Char
digit = oneOf ['0'..'9']

number :: CalcParser Integer
number = read <$> many1 digit

fp_char :: CalcParser String 
fp_char = many1 digit


applyMany :: a -> [a -> a] -> a
applyMany x [] = x
applyMany x (h:t) = applyMany (h x) t


div_ :: CalcParser (Integer -> Integer -> Integer)
div_= do
    char '/'
    return div

star :: CalcParser (Integer -> Integer -> Integer)
star = do
    char '*'    
    return (*)  

plus :: CalcParser (Integer -> Integer -> Integer)
plus = do
    char '+'    
    return (+)

minus :: CalcParser (Integer -> Integer -> Integer)
minus = do
    char '-'    
    return (-)  

multiply :: CalcParser Integer
multiply = do
    spaces
    lhv <- enclosed
    spaces
    t <- many tail
    return $ applyMany lhv t
    where tail =
                do
                    f <- star <|> div_
                    spaces
                    rhv <- enclosed
                    spaces
                    return (`f` rhv)


add :: CalcParser Integer
add = do
    spaces
    lhv <- multiply <|> fact' <|> negation'
    spaces
    t <- many tail
    return $ applyMany lhv t
    where tail =
                do
                    f <- plus <|> minus
                    spaces
                    rhv <- multiply <|> fact' <|> negation'
                    spaces
                    return (`f` rhv)

enclosed :: CalcParser Integer
enclosed = number <|> do
    char '('
    res <- add
    char ')'
    return res

-- factorial    
fact' :: CalcParser Integer
fact' = do
    spaces
    char '!'
    rhv <- number
    return $ factorial rhv  

factorial :: Integer -> Integer
factorial n
    | n < 0 = error "No factorial exists for negative inputs" 
    | n == 0 || n == 1 = 1
    | otherwise = acc n 1
    where
    acc 0 a = a
    acc b a = acc (b-1) (b * a)  

-- negation 
negation' :: CalcParser Integer
negation' = do
    spaces
    char '~'
    rhv <- enclosed
    spaces
    return $ negate rhv

В приведенный выше список включены определения функций для основных операций, расширенных опциями отрицания и факториального вычисления. Что мне нужно, это просто сделать эту программу чувствительной к значениям с плавающей запятой, а также к целым числам в любом вводе строки. Как я могу реализовать это для запуска парсера, вызывая единственную функцию (применимую как к дробным, так и к целым числам) следующим образом (только для примера):

parseTest totalCalc "~(8.44 * 12.85 / 3.2) * !4"

Я ввожу '!' символ перед цифрой c частью факториальной нотации, потому что синтаксический анализатор не распознает нормальное '4!' или аналогичные последовательности символов в качестве факторных показателей.

1 Ответ

1 голос
/ 15 января 2020

Шаг 1: поиск + замена всех вхождений от Integer до Double. Теперь ваш синтаксический анализатор все еще может читать только целые числа, но внутренне он будет представлять их как Double s.

Шаг 2: заставляет анализатор number анализировать либо целое число, либо плавающее Точка первая. Целое число, которое вы уже записали: это просто последовательность цифр. Давайте переименуем его, чтобы лучше отразить то, что он делает:

parseInt :: CalcParser Double
parseInt = read <$> many1 digit

С плавающей точкой не намного сложнее: это последовательность цифр, за которой следует точка (точка), за которой следует другая последовательность цифр:

parseDouble :: CalcParser Double
parseDouble = do
    whole <- many1 digit
    char '.'
    fract <- many1 digit
    pure $ read $ whole <> "." <> fract

А затем любой номер будет просто "double или int":

number :: CalcParser Double
number = try parseDouble <|> parseInt

Еще две ноты:

Первый , обратите внимание, что сначала вы должны попробовать двойной. Если вы этого не сделаете, строка "8.32" будет проанализирована как int, потому что префикс "8" соответствует правилам для parseInt.

Second , обратите внимание, что у вас есть использовать try с parseDouble. Если вы этого не сделаете, целые числа не будут проанализированы, потому что парсер parseDouble будет использовать ввод до конца цифр, не увидит точку, потерпит неудачу, но не откатится до начало цифр , так что parseInt не увидит никаких цифр и тоже потерпит неудачу. Комбинатор try - это то, что выполняет откат к началу при сбое синтаксического анализатора.

...