This commit is contained in:
det-fys 2024-12-28 14:31:14 +01:00
parent 6678a45c7d
commit 5235189966
2 changed files with 175 additions and 10 deletions

7
.gitignore vendored
View File

@ -7,3 +7,10 @@ build/
*.svg *.svg
*.ps *.ps
*.pdf *.pdf
*.aux
*.fdb_latexmk
*.fls
*.log
*.out
*.synctex.gz
*.toc

View File

@ -25,13 +25,34 @@
\usepackage{graphicx} \usepackage{graphicx}
\graphicspath{{Images/}} % Při vkládání obrázků se bude prefixovat tato relativní cesta. \graphicspath{{Images/}} % Při vkládání obrázků se bude prefixovat tato relativní cesta.
% \usepackage{inconsolata} % \usepackage{zi4}
% \usepackage{courier}
% Při použití tohoto balíku začnou fungovat odkazy v textu. % Při použití tohoto balíku začnou fungovat odkazy v textu.
% Zkuste třeba kliknout na odkazy v textu (např. "1.1" na straně 2) nebo v seznamu obrázků/tabulek. % Zkuste třeba kliknout na odkazy v textu (např. "1.1" na straně 2) nebo v seznamu obrázků/tabulek.
% `hidelinks` skryje ošklivé výchozí rámečky kolem odkazů. % `hidelinks` skryje ošklivé výchozí rámečky kolem odkazů.
\usepackage[hidelinks]{hyperref} \usepackage[hidelinks]{hyperref}
\usepackage{listings}
% \usepackage{minted}
\usepackage{xcolor}
% Define code snippet settings
\lstset{
language=C, % Set the programming language for the code snippet
basicstyle=\ttfamily, % Set the font for the code
keywordstyle=\color{blue}, % Set color for keywords
commentstyle=\color{green!60!black}, % Set color for comments
stringstyle=\color{red}, % Set color for strings
% numbers=left, % Show line numbers
% numberstyle=\tiny\color{gray}, % Style for line numbers
breaklines=true, % Enable line breaks
% frame=single,
showstringspaces=false % Don't show spaces in strings
}
% Začátek dokumentu % Začátek dokumentu
\begin{document} \begin{document}
@ -81,8 +102,8 @@ zvoleném definičním oboru.
Celé zadání je k~dispozici na \url{https://www.kiv.zcu.cz/studies/predmety/pc/data/works/sw2024-02.pdf}. Celé zadání je k~dispozici na \url{https://www.kiv.zcu.cz/studies/predmety/pc/data/works/sw2024-02.pdf}.
\section{Analýza úlohy} \section{Analýza úlohy}
Úloha se skládá ze dvou hlavních problémů: matematickou funkci je nejprve třeba analyzovat a~poté Práce se zabývá dvěma hlavními úlohami: matematickou funkci je nejprve třeba analyzovat a~poté
použít výstup této analýzy ke zjištění jejích hodnot v~jednotlivých bodech a~vykreslení grafu. použít výstup této analýzy k její evaluaci a~vykreslení grafu.
\subsection{Analýza funkce} \subsection{Analýza funkce}
Analýzu zadané matematické funkce je vhodné rozdělit do dvou částí --- analýzu lexikální a~syntaktickou. Analýzu zadané matematické funkce je vhodné rozdělit do dvou částí --- analýzu lexikální a~syntaktickou.
@ -99,9 +120,10 @@ Typy tokenů využité v~této práci jsou uvedeny v~tabulce~\ref{tab:tokens}.
\begin{table}[] \begin{table}[]
\centering \centering
\caption{Použité typy tokenů}\label{tab:tokens}
\begin{tabular}{|l|l|} \begin{tabular}{|l|l|}
\hline \hline
označení & popis \\ \hline \textbf{označení} & \textbf{popis} \\ \hline
\texttt{NUMBER} & konstanta \\ \hline \texttt{NUMBER} & konstanta \\ \hline
\texttt{PLUS} & operátor sčítání \texttt{+} \\ \hline \texttt{PLUS} & operátor sčítání \texttt{+} \\ \hline
\texttt{MINUS} & operátor odečítání nebo negace \texttt{-} \\ \hline \texttt{MINUS} & operátor odečítání nebo negace \texttt{-} \\ \hline
@ -116,21 +138,19 @@ Typy tokenů využité v~této práci jsou uvedeny v~tabulce~\ref{tab:tokens}.
\texttt{EOF} & konec vstupu \\ \hline \texttt{EOF} & konec vstupu \\ \hline
\texttt{ERROR} & chyba (nerozpoznatelná sekvence) \\ \hline \texttt{ERROR} & chyba (nerozpoznatelná sekvence) \\ \hline
\end{tabular} \end{tabular}
\caption{Použité typy tokenů}
\label{tab:tokens}
\end{table} \end{table}
\subsubsection{Syntaktická analýza} \subsubsection{Syntaktická analýza}
Následuje analýza syntaktická, během které je zadaná funkce zpracována do stromové struktury, kterou je pak možné Následuje analýza syntaktická, během které je zadaná funkce zpracována do stromové struktury.
využít pro vyhodnocování funkce v~jednotlivých bodech.
Existuje mnoho způsobů, jak tuto analýzu provést, mezi základní patří např.~rekurzivní sestup nebo algoritmus shunting-yard. Existuje mnoho způsobů, jak tuto analýzu provést, mezi základní patří např.~rekurzivní sestup nebo algoritmus shunting-yard.
V~této práci je použita metoda rekurzivního sestupu, která je relativně jednoduchá a~přehledná --- program je možné V~této práci je použita metoda rekurzivního sestupu, která je relativně jednoduchá a~přehledná --- program je možné
mechanicky vytvořit z~gramatiky zpracovávaného jazyka. Mezi její další výhody patří, že se oproti mechanicky vytvořit z~gramatiky zpracovávaného jazyka. Mezi její další výhody patří, že se oproti
algoritmu shunting-yard dokáže lépe vypořádat s~unárními operátory. algoritmu shunting-yard dokáže lépe vypořádat s~unárními operátory.
Analyzátor rekurzivním sestupem lze obecně vytvořit z~gramatiky popisující zpracovávaný jazyk Analyzátor rekurzivním sestupem lze obecně vytvořit z~gramatiky popisující zpracovávaný jazyk
pomocí sady funkcí, které odpovídají jednotlivým pravidlům gramatiky. To je v~tomto případě pomocí sady funkcí, které odpovídají jednotlivým pravidlům této gramatiky.
snadné, protože pro zpracování matematického výrazu lze sestavit gramatiku
Pro zpracování matematických lze sestavit gramatiku
\begin{verbatim} \begin{verbatim}
<expression> = <term> { ( PLUS | MINUS ) <term> } <expression> = <term> { ( PLUS | MINUS ) <term> }
@ -149,4 +169,142 @@ kde \verb|{}| značí iteraci, \verb|[]| volitelnost a~\texttt{|} jednu z~možno
Výrazy v~\verb|<>| jsou neterminály, zatímco ostatní symboly jsou terminály odpovídající Výrazy v~\verb|<>| jsou neterminály, zatímco ostatní symboly jsou terminály odpovídající
tokenům z~lexikální analýzy. tokenům z~lexikální analýzy.
Tato gramatika je typu LL(1), což znamená, že je možné se při zpracování
vždy rozhoudnout pouze na základě jednoho symbolu ze~vstupu, což zjednodušuje
implementaci analyzátoru.
V~případě, že je zadaná funkce syntakticky správná, je během této analýzy možné
vytvořit stromovou strukturu, kde bude každý uzel
reprezentovat jednu hodnotu nebo matematickou operaci a~jeho potomci budou odpovídat
jejím operandům.
Tuto strukturu lze pak využít pro vyhodnocování zadané funkce v~jednotlivých bodech,
což je klíčové pro vykreslení grafu. Příklad stromové struktury pro funkci $\sin(2x)+ 1$
je zobrazen na obrázku~\ref{fig:graph}.
\begin{figure}[]
\centering
\includegraphics[width=0.6\textwidth]{graph}
\caption{Strom výrazu $\sin(2x) + 1$}\label{fig:graph}
\end{figure}
\subsection{Vykreslení grafu}
Graf je možné vykreslit pomocí lomené čáry. To lze v~jazyce PostScript realizovat vytvořením
cesty pomocí příkazů \texttt{moveto} a~\texttt{lineto} a~jejím vykreslením příkazem \texttt{stroke}.
Body, které budou tvořit lomenou čáru, budou zřejmě ve tvaru $(x, f(x))$, kde $f(x)$ je hodnota
zadané funkce v~bodě $x$. Zbývá otázka, jaké hodnoty vybrat pro $x$. Jednou z možností je zvolit
pevně danou šířku kroku, např.~$d = 0.1$, a vyhodnocovat funkci v~bodech $x = x_d + kd$,
kde $k$ postupně nabývá hodnot $0, 1, 2, \ldots$, dokud $x_d + kd \leq x_h$, a kde $x_d$ je dolní
a~$x_h$ horní mez zadaného intervalu pro vykreslení grafu. Nevýhodou tohoto přístupu je, že
nebere v~potaz délku zadaného intervalu, což může vést k~příliš malému počtu bodů v~případě malého intervalu a~naopak.
Lepší variantou je tedy namísto pevného kroku $d$ zvolit pevný počet bodů~$n$,
které budou tvořit lomenou čáru. V~takovém případě je krok možné určit jako $d = (x_h - x_d) / n$.
V implementaci je také třeba vyřešit body, kde funkce není definována.
\section{Popis implementace}
Program je rozdělen do několika modulů. Každý z~nich má vlastní rozhraní, a~implementace
každého modulu je nezávislá na implementaci ostatních modulů.
Každý modul je popsán v~následujících podsekcích včetně popisu jeho rozhraní.
\subsection{Vstupní bod programu --- \texttt{main.c}}
Výkon programu začíná ve~funkci \texttt{main}, která má na starosti následující úkoly:
\begin{itemize}
\item Ověření správného počtu vstupních argumentů. V~případě, že je počet argumentů
neplatný, program vypíše návod k~použití pomocí funkce \texttt{print\_usage} a~ukončí se.
\item Načtení rozsahu pro vykreslení grafu pomocí funkce \texttt{parse\_range}. Pokud
tento argument není uveden, použije se výchozí rozsah $x_d = y_d = -10$ a~$x_h = y_h = 10$.
\item Zpracování zadané funkce pomocí modulu \texttt{parser}, který vytvoří strom
reprezentující tuto funkci.
\item Vykreslení grafu funkce na zadaném rozsahu pomocí modulu \texttt{ps\_graph}
do zadaného souboru. K~tomuto účelu je zavolána funkce \texttt{export\_to\_file}.
\item \textit{Pokud je program zkompilován s~definicí makra \texttt{ENABLE\_GRAPHVIZ\_EXPORT}, je
strom výrazu vyexportován do souboru ve formátu \texttt{dot}.}
\item Uvolnění alokovaných zdrojů a vrácení návratového kódu.
\end{itemize}
\subsection{Chyby --- modul \texttt{error\_buffer}}
\subsection{Lexikální analyzátor --- modul \texttt{lexer}}
Lexikální analyzátor se stará o~tokenizaci vstupního řetězce.
\subsubsection{Výčet \texttt{token\_type}}
Typy tokenů jsou reprezentovány výčtem \texttt{token\_type}, který obsahuje
všechny typy tokenů uvedené v~tabulce~\ref{tab:tokens}.
Názvy typů odpovídají označením v~tabulce s~prefixem \texttt{TOK\_}.
\subsubsection{Struktura \texttt{token}}
Token je reprezentován strukturou \texttt{token}, která obsahuje typ tokenu a~případně
jeho hodnotu, pokud je to vzhledem k~jeho typu relevantní.
\subsubsection{Struktura \texttt{lexer}}
Struktura \texttt{lexer} obsahuje stav lexikálního analyzátoru, tj.~ukazatel na zpracovávaný
řetězec, ukazatel na aktuální pozici v~tomto řetězci, aktuální token a~další informace potřebné pro zpracování.
\subsubsection{Inicializace}
Funkce
\begin{lstlisting}
void lex_init(struct lexer *lex, const char *str, const char *variable_name);
\end{lstlisting}
% \texttt{void lex\_init(struct lexer *lex, const char *str, const char *variable\_name);}
inicializuje lexikální analyzátor \texttt{lex} pro zpracování řetězce \texttt{str}.
\texttt{variable\_name} je název proměnné, která se ve vstupním řetězci může vyskytnout.
Deinicializace není třeba, protože při inicializaci ani činnosti tohoto modulu není prováděna žádná alokace paměti.
\subsubsection{Získávání tokenů}
Aktuální token je možné získat pomocí funkce
\begin{lstlisting}
struct token *lex_token(struct lexer *lex);
\end{lstlisting}
která na něj vrací ukazatel.
Pro získání dalšího tokenu je třeba zavolat funkci
\begin{lstlisting}
void lex_next(struct lexer *lex);
\end{lstlisting}
\subsubsection{Chyba při lexikální analýze}
Pokud nastane během lexikální analýzy chyba, tzn.~v~řetězci se vyskytne sekvence znaků,
kterou nelze převést na token, je vytvořen token typu \texttt{TOK\_ERROR}.
V~tomto případě je možné pomocí funkce
\begin{lstlisting}
enum error_code lex_get_error(
const struct lexer *lex);
\end{lstlisting}
získat chybový kód.
Pro získání textové reprezentace chyby lze využít funkci
\begin{lstlisting}
const char *lex_get_error_text(
const struct lexer *lex);
\end{lstlisting}
\subsubsection{Diagnostika}
Pro diagnostické účely je možné získat textovou reprezentaci typu tokenu funkcí
\begin{lstlisting}
const char *lex_token_str(enum token_type token);
\end{lstlisting}
V~případě, že nastane chyba při dalším zpracování, je možné přehledně vypsat
aktuální pozici v~řetězci pomocí funkce
\begin{lstlisting}
void lex_print_position(const struct lexer *lex, struct error_buffer *eb);
\end{lstlisting}
která ji vypíše do zásobníku \texttt{eb}.
\subsection{Syntaktický analyzátor --- modul \texttt{parser}}
\subsection{Strom výrazu --- modul \texttt{tree}}
\subsection{Vykreslení grafu --- modul \texttt{ps\_graph}}
\section{Uživatelská příručka}
\section{Závěr}
\end{document} \end{document}