Особенности реализации первой части в отчёте

This commit is contained in:
2024-12-09 22:10:09 +03:00
parent 9406d82433
commit 5caf07c35c

View File

@@ -209,15 +209,169 @@
\end{enumerate}
\newpage
\section {Математическое описание}
% \newpage
% \section {Математическое описание}
\newpage
\section {Особенности реализации}
\section{Особенности реализации}
Согласно заданию для каждой части работы был создан отдельный проект \texttt{stack}.
\subsection{Часть 1: Синтаксический анализ арифметических выражений}
\subsubsection{Тип Parser}
Тип \texttt{Parser} обеспечивает разбор входной строки по заданным правилам. Он принимает на вход список токенов (например, символов) и пытается разобрать их в соответствии с описанными правилами, возвращая либо результат с оставшейся частью строки, либо \texttt{Nothing}, если разбор не удался.
Код типа \texttt{Parser} представлен в листинге~\ref{lst:parser_type}.
\begin{itemize}
\item Вход: список токенов.
\item Выход: результат разбора в виде \texttt{Maybe ([tok], a)}.
\end{itemize}
Для типа \texttt{Parser} определены представители классов типов для \texttt{Functor}, \texttt{Applicative} и \texttt{Alternative}. Представитель \texttt{Functor} позволяет применять функцию к результату разбора парсера. Представители \texttt{Applicative} и \texttt{Alternative} позволяют последовательно комбинировать разные парсеры и функции и составлять сложные парсеры из простых.
\begin{lstlisting}[caption={Определение типа Parser и его представителей для классов типов Functor, Applicative и Alternative.}, label={lst:parser_type}]
newtype Parser tok a =
Parser { runParser :: [tok] -> Maybe ([tok], a)}
instance Functor (Parser tok) where
fmap g (Parser p) = Parser $ \xs ->
case p xs of
Nothing -> Nothing
Just (cs, c) -> Just (cs, g c)
instance Applicative (Parser tok) where
pure x = Parser $ \toks -> Just (toks, x)
Parser u <*> Parser v = Parser $ \xs ->
case u xs of
Nothing -> Nothing
Just (xs', g) ->
case v xs' of
Nothing -> Nothing
Just (xs'', x) -> Just (xs'', g x)
instance Alternative (Parser tok) where
empty = Parser $ \_ -> Nothing
Parser u <|> Parser v = Parser $ \xs ->
case u xs of
Nothing -> v xs
z -> z
\end{lstlisting}
\subsubsection{Работа с арифметическими операциями}
В листинге~\ref{lst:Operation} представлен код определения класса \texttt{Operation}, а также нескольких вспомогательных функций для работы с ним. Тип используется для хранения одной из четырёх арифметических операций: сложение, вычитание, умножение и деление. Функция \texttt{operationToString} принимает значение типа \texttt{Operation} и возвращает его строковое представление. Функция \texttt{operationToOperator} также принимает значение типа \texttt{Operation}, а возвращает функцию, соответствующую арифметической операции.
\begin{lstlisting}[caption={Определение типа Operation и функций для работы с ним.}, label={lst:Operation}]
data Operation = Add | Sub | Mul | Div deriving Show
operationToString :: Operation -> String
operationToString op = case op of
Add -> "+"
Sub -> "-"
Mul -> "*"
Div -> "/"
operationToOperator :: Operation -> (Int -> Int -> Int)
operationToOperator op = case op of
Add -> (+)
Sub -> (-)
Mul -> (*)
Div -> div
\end{lstlisting}
\subsubsection{Базовые парсеры}
В этом разделе рассматриваются основные парсеры, используемые для разбора арифметических выражений. Эти парсеры являются строительными блоками для более сложных выражений. Их код представлен в листинге~\ref{lst:base_parsers}.
\begin{itemize}
\item \texttt{satisfy} — парсит символ, удовлетворяющий предикату, и возвращает его.
\item \texttt{char} — парсит один заданный символ и возвращает его.
\item \texttt{digit} — парсит одну цифру и возвращает в виде символа.
\item \texttt{skipSpaces} — парсит все пробелы пока не встретить символ, который пробелом не является.
\item \texttt{number} — парсит целое число (последовательность цифр) и возвращает в виде~\texttt{Int}.
\item \texttt{operation} — парсит арифметическую операцию (\texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}) и возвращает как значение типа \texttt{Operation}.
\end{itemize}
\begin{lstlisting}[caption={Базовые парсеры}, label={lst:base_parsers}]
satisfy :: (tok -> Bool) -> Parser tok tok
satisfy pr = Parser $ \case
(c:cs) | pr c -> Just (cs, c)
_ -> Nothing
char :: Char -> Parser Char Char
char c = satisfy (== c)
digit :: Parser Char Char
digit = satisfy isDigit
skipSpaces :: Parser Char String
skipSpaces = many (char ' ')
number :: Parser Char Int
number = skipSpaces *> (strToInt <$> some digit)
where
strToInt = foldl (\acc x -> acc * 10 + digitToInt x) 0
operation :: Parser Char Operation
operation = skipSpaces *> (
char '+' *> pure Add <|>
char '-' *> pure Sub <|>
char '*' *> pure Mul <|>
char '/' *> pure Div
)
\end{lstlisting}
\subsubsection{Парсер expression}
Парсер \texttt{expression}, код которого представлен в листинге~\ref{lst:expression}, парсит выражение вида \texttt{<число> <операция> <число>}. В случае успеха возвращает кортеж вида: \texttt{(Int -- левый операнд, Operation -- операция, Int -- правый операнд)}. Является комбинацей парсеров \texttt{number} и \texttt{operation}. Не чувствителен к пробелам до выражения и внутри него, между операндами и оператором. Поглощает также пробелы после выражения с помощью парсера \texttt{skipSpaces}.
\begin{lstlisting}[caption={Код функции expression}, label={lst:expression}]
expression :: Parser Char (Int, Operation, Int)
expression = (,,) <$> number <*> operation <*> number <* skipSpaces
\end{lstlisting}
\subsubsection{Функция processExpression}
Код функции \texttt{processExpression} представлен в листинге~\ref{lst:processExpression}.
Функция принимает строку, парсит её как выражение, вычисляет результат и возвращает строку с ответом. При ошибке парсинга генерирует ошибку.
Вход: \texttt{String} — строка с выражением.
Выход: \texttt{String} — результат вычисления в формате \texttt{a op b = result}.
Вспомогательная функция \texttt{calculateExpression} используется для вычисления результата. На вход она получает операнды и операцию, а возвращает вычисленное значение. Её код также представлен в листинге~\ref{lst:processExpression}.
\begin{lstlisting}[caption={Код функции processExpression}, label={lst:processExpression}]
processExpression :: String -> String
processExpression s = case runParser expression s of
Nothing -> error $ "Не удалось прочитать выражение: \"" ++ s ++ "\""
Just (cs, (a, op, b)) -> case cs of
[] -> show a ++ " " ++ operationToString op ++ " " ++
show b ++ " = " ++ show (calculateExpression (a, op, b)) ++ "\n"
_ -> error $ "Не удалось прочитать выражение: \"" ++ s ++ "\""
calculateExpression :: (Int, Operation, Int) -> Int
calculateExpression (a, op, b) = (operationToOperator op) a b
\end{lstlisting}
\subsubsection{Функция main}
Код функции \texttt{main} представлен в листинге~\ref{lst:main}.
Функция \texttt{main} считывает имя файла у пользователя, читает файл, построчно обрабатывает каждое выражение с помощью \texttt{processExpression} и выводит результат.
\begin{lstlisting}[caption={Код функции main}, label={lst:main}]
main :: IO ()
main =
putStrLn "Введите имя файла:" >>
getLine >>= \fileName ->
readFile fileName >>= \content ->
let expressions = lines content in
putStrLn $ concatMap processExpression expressions
\end{lstlisting}
\subsection{Часть 2: Синтаксический анализ текста и генерация фраз}