This commit is contained in:
det-fys 2024-12-30 19:09:19 +01:00
parent 8d587bf4d1
commit 936c846b0c

View File

@ -33,6 +33,7 @@
% 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ů.
\usepackage[hidelinks]{hyperref}
\usepackage{bookmark}
\usepackage{listings}
% \usepackage{minted}
@ -122,25 +123,26 @@ Typy tokenů využité v~této práci jsou uvedeny v~tabulce~\ref{tab:tokens}.
\centering
\caption{Použité typy tokenů}\label{tab:tokens}
\begin{tabular}{|l|l|}
\hline
\textbf{označení} & \textbf{popis} \\ \hline
\texttt{NUMBER} & konstanta \\ \hline
\texttt{PLUS} & operátor sčítání \texttt{+} \\ \hline
\texttt{MINUS} & operátor odečítání nebo negace \texttt{-} \\ \hline
\texttt{MULTIPLY} & operátor násobení \texttt{*} \\ \hline
\texttt{DIVIDE} & operátor dělení \texttt{*} \\ \hline
\texttt{POWER} & operátor umocnění \verb|^| \\ \hline
\texttt{VARIABLE} & proměnná \texttt{x} \\ \hline
\texttt{FUNCTION} & název funkce \\ \hline
\texttt{LEFT\_PAREN} & levá závorka \verb|(| \\ \hline
\texttt{RIGHT\_PAREN} & pravá závorka \verb|)| \\ \hline
\texttt{COMMA} & oddělovač argumentů \texttt{,} \\ \hline
\texttt{EOF} & konec vstupu \\ \hline
\texttt{ERROR} & chyba (nerozpoznatelná sekvence) \\ \hline
\hline
\textbf{označení} & \textbf{popis} \\ \hline
\texttt{NUMBER} & konstanta \\ \hline
\texttt{PLUS} & operátor sčítání \texttt{+} \\ \hline
\texttt{MINUS} & operátor odečítání nebo negace \texttt{-} \\ \hline
\texttt{MULTIPLY} & operátor násobení \texttt{*} \\ \hline
\texttt{DIVIDE} & operátor dělení \texttt{*} \\ \hline
\texttt{POWER} & operátor umocnění \verb|^| \\ \hline
\texttt{VARIABLE} & proměnná \texttt{x} \\ \hline
\texttt{FUNCTION} & název funkce \\ \hline
\texttt{LEFT\_PAREN} & levá závorka \verb|(| \\ \hline
\texttt{RIGHT\_PAREN} & pravá závorka \verb|)| \\ \hline
\texttt{COMMA} & oddělovač argumentů \texttt{,} \\ \hline
\texttt{EOF} & konec vstupu \\ \hline
\texttt{ERROR} & chyba (nerozpoznatelná sekvence) \\ \hline
\end{tabular}
\end{table}
\subsubsection{Syntaktická analýza}
\label{sec:ana}
Následuje analýza syntaktická, během které je zadaná funkce zpracována do stromové struktury.
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é
@ -204,9 +206,9 @@ 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ého modulu je (až na výjimky) 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í.
Každý modul včetně jeho rozhraní je popsán v~následujících podsekcích.
\subsection{Vstupní bod programu --- \texttt{main.c}}
@ -226,7 +228,158 @@ Výkon programu začíná ve~funkci \texttt{main}, která má na starosti násle
\item Uvolnění alokovaných zdrojů a vrácení návratového kódu.
\end{itemize}
\subsection{Chyby --- modul \texttt{error\_buffer}}
\subsection{Chyby --- modul \texttt{errors}}
Modul \texttt{errors} obsahuje definici chybových kódů a~nástroje pro výpis chyb.
\subsubsection{Výčet \texttt{error\_code}}
Výčet \texttt{error\_code} obsahuje kódy všech chyb, které mohou během vykonávání programu nastat.
Číselné hodnoty chybových kódů jsou zároveň návratovými kódy programu.
Konkrétní hodnoty jsou uvedeny v tabulce \ref{tab:errors}.
\begin{table}[]
\centering
\caption{Chybové kódy}\label{tab:errors}
\begin{tabular}{|l|c|l|}
\hline
\textbf{název konstanty} & \textbf{hodnota} & \textbf{význam} \\ \hline
\texttt{ERR\_NO\_ERR} & 0 & žádná chyba \\ \hline
\texttt{ERR\_INVALID\_ARGS} & 1 & byly zadány neplatné argumenty programu \\ \hline
\texttt{ERR\_INVALID\_FUNCTION} & 2 & neplatný zápis matematické funkce \\ \hline
\texttt{ERR\_INVALID\_FILENAME} & 3 & neplatný název souboru \\ \hline
\texttt{ERR\_INVALID\_LIMITS} & 4 & zadané hranice jsou ve špatném formátu \\ \hline
\texttt{ERR\_BAD\_ALLOC} & 5 & chyba při alokaci paměti \\ \hline
\end{tabular}
\end{table}
\subsubsection{Struktura \texttt{error\_buffer}}
Struktura \texttt{error\_buffer} slouží k~uchování chybových hlášek. Obsahuje chybový kód a~zásobník
pro textovou reprezentaci chyby.
Důvodem pro použití zásobníku je, že není vždy vhodné ihned vypsat chybovou hlášku do standardního výstupu,
zejména při použití některých modulů jako knihoven. Příklad tohoto použití je popsán
v~sekci~\ref{sec:upg}.
\subsubsection{Inicializace}
Funkce
\begin{lstlisting}
void error_buffer_init(struct error_buffer *eb);
\end{lstlisting}
inicializuje chybový zásobník \texttt{eb}.
\subsubsection{Vyhození chyby}
Chybový kód je možné nastavit pomocí funkce
\begin{lstlisting}
void error_set(struct error_buffer *eb,
enum error_code err);
\end{lstlisting}
Funkcí
\begin{lstlisting}
void error_printf(struct error_buffer *eb,
const char *format, ...);
\end{lstlisting}
je možné do zásobníku vložit textovou zprávu.
\texttt{format} je formátovací řetězec se standardní syntaxí funkce \texttt{printf}
(interně je použita funkce \texttt{vsnprintf}).
Pro získání chybového kódu a zprávy je pak možné použít funkce
\begin{lstlisting}
enum error_code error_get(
const struct error_buffer *eb);
const char *error_get_text(
const struct error_buffer *eb);
\end{lstlisting}
\subsection{Strom výrazu --- modul \texttt{tree}}
Modul \texttt{tree} obsahuje definici stromu výrazu a nástroje pro jeho manipulaci.
\subsubsection{Struktura \texttt{expr\_node}}
Struktura \texttt{expr\_node} reprezentuje uzel stromu výrazu. Obsahuje typ uzlu a ukazatele na další uzly:
v~případě binární nebo unární operace na její operandy, v~případě funkce na argumenty. Pokud je uzel konstanta, obsahuje místo
toho její číselnou hodnotu.
\subsubsection{Vytvoření uzlu}
Pro vytvoření uzlu je možné použít funkce, jejichž názvy začínají \texttt{node\_create\_}:
\texttt{node\_create\_const} (konstanta), \texttt{node\_create\_x} (proměnná), \texttt{node\_create\_add} (sčítání),
\texttt{node\_create\_sub} (odečítání), \texttt{node\_create\_mult} (násobení), \texttt{node\_create\_div} (dělení),
\texttt{node\_create\_pow} (umocnění), \texttt{node\_create\_neg} (negace), \texttt{node\_create\_fn} (funkce).
Většina těchto funkcí přijímá jako argumenty ukazatele na uzly, které se mají stát operandy vytvářeného uzlu.
\subsubsection{Evaluace uzlu}
Uzel lze vyhodnotit v~daném bodě pomocí funkce
\begin{lstlisting}
enum eval_result node_eval(
const struct expr_node *node,
double x, double *y);
\end{lstlisting}
která v~případě úspěchu vrací \texttt{EVAL\_OK} a~do \texttt{y} uloží funkční hodnotu. Pokud
funkce není v tomto bodě definována, je výsledkem \texttt{EVAL\_ERROR}.
\subsubsection{Uvolnění uzlu}
Uzel je možné uvolnit pomocí funkce
\begin{lstlisting}
void node_free(struct expr_node *node);
\end{lstlisting}
Tato funkce rekurzivně uvolní i~všechny potomky.
\subsection{Matematické funkce --- modul \texttt{math\_functions}}
Modul \texttt{math\_functions} obsahuje implementace matematických funkcí, které lze použít
ve vstupním výrazu.
Každá matematická funkce je vyhodnocována jednou funkcí jazyka C. Argumenty každé z těchto funkcí jsou
pole argumentů \lstinline|const double *args| a~ukazatel na výsledek \lstinline|double *y|.
Tyto funkce vrací hodnotu typu \lstinline|enum eval_result| indikující úspěch nebo chybu při vyhodnocení.
\subsubsection{Struktura \texttt{math\_function}}
Struktura \texttt{math\_function} obsahuje název funkce, ukazatel na C funkci výše uvedeného typu, která tuto matematickou funkci vyhodnotí
a~počet argumentů, které tato funkce přijímá.
\subsubsection{Použití}
Pomocí funkce
\begin{lstlisting}
const struct math_function *fns_get(void);
\end{lstlisting}
je možné získat pole všech podporovaných funkcí.
Toto pole je ukončeno položkou s~názvem \texttt{NULL}.
Obsah tohoto pole je během vykonávání programu neměnný, což znamená, že je možné
použít index do tohoto pole jako identifikátor konkrétní matematické funkce.
Toho je využíváno během tokenizace výrazu, kde se index stane hodnotou příslušného tokenu.
V~současné implementaci je dokonce tento seznam natvrdo zakódován v~této funkci.
Bylo by však možné umožnit registraci nových funkcí externě.
\subsubsection{Implementované matematické funkce}
Jsou podporovány funkce jedné nebo více proměnných, které jsou uvedeny v~tabulce~\ref{tab:math_functions}.
\begin{table}[]
\centering
\caption{Implementované matematické funkce}\label{tab:math_functions}
\begin{tabular}{|c|c|c|}
\hline
\textbf{název} & \textbf{počet arg.} & \textbf{význam} \\ \hline
\texttt{abs} & 1 & $|x_0|$ \\ \hline
\texttt{exp} & 1 & $e^{x_0}$ \\ \hline
\texttt{ln} & 1 & $\ln(x_0)$ \\ \hline
\texttt{log} & 1 & $\log_{10}(x_0)$ \\ \hline
\texttt{sin} & 1 & $\sin(x_0)$ \\ \hline
\texttt{cos} & 1 & $\cos(x_0)$ \\ \hline
\texttt{tan} & 1 & $\tan(x_0)$ \\ \hline
\texttt{asin} & 1 & $\arcsin(x_0)$ \\ \hline
\texttt{acos} & 1 & $\arccos(x_0)$ \\ \hline
\texttt{atan} & 1 & $\arctan(x_0)$ \\ \hline
\texttt{sinh} & 1 & $\sinh(x_0)$ \\ \hline
\texttt{cosh} & 1 & $\cosh(x_0)$ \\ \hline
\texttt{tanh} & 1 & $\tanh(x_0)$ \\ \hline
\texttt{min} & 2 & $\min(x_0, x_1)$ \\ \hline
\texttt{max} & 2 & $\max(x_0, x_1)$ \\ \hline
\texttt{mod} & 2 & zbytek po dělení $x_0$ číslem $x_1$ \\ \hline
\texttt{sgn} & 1 & $\mathop{\mathrm{sgn}}(x_0)$ \\ \hline
\texttt{floor} & 1 & $\lfloor x_0 \rfloor$ \\ \hline
\texttt{ceil} & 1 & $\lceil x_0 \rceil$ \\ \hline
\end{tabular}
\end{table}
\subsection{Lexikální analyzátor --- modul \texttt{lexer}}
Lexikální analyzátor se stará o~tokenizaci vstupního řetězce.
@ -259,7 +412,8 @@ Deinicializace není třeba, protože při inicializaci ani činnosti tohoto mod
\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);
const struct token *lex_token(
const struct lexer *lex);
\end{lstlisting}
která na něj vrací ukazatel.
Pro získání dalšího tokenu je třeba zavolat funkci
@ -296,15 +450,150 @@ aktuální pozici v~řetězci pomocí funkce
která ji vypíše do zásobníku \texttt{eb}.
\subsection{Syntaktický analyzátor --- modul \texttt{parser}}
Modul \texttt{parser} využívá tokeny získané lexikálním analyzátorem k~syntaktické
analýze a~vytváří stromu zadaného výrazu.
\subsubsection{Postup analýzy}
Syntantický analyzátor je implementován pomocí rekurzivního sestupu.
Základem je gramatika uvedená v~sekci~\ref{sec:ana}.
Pro každý neterminál z~této gramatiky existuje jedna funkce, jejíž název
se skládá z~prefixu \texttt{parse\_} a~názvu neterminálu:
\begin{itemize}
\item Analýza začíná ve funkci \texttt{parse\_expression}. Tato funkce volá
\texttt{parse\_term} na začátku a~poté opakovaně, vždy pokud po předchozím zavolání následuje
operátor sčítání nebo odečítání. Pro každý takový operátor je vytvořen příslušný uzel. Operandy prvního takového
uzlu jsou výsledky volání \texttt{parse\_term}. Operandy každého dalšího jsou předchozí vytvořený uzel a~další uzel vrácený \texttt{parse\_term}.
Pokud je tedy výrazem například sečtení tří podvýrazů, jsou vytvořeny dva uzly sčítání: operandy prvního z nich jsou dva tyto podvýrazy a operandy druhého
jsou první uzel a~třetí podvýraz. Poslední uzel je návratovou hodnotou.
\item Funkce \texttt{parse\_term} postupuje obdobně jako \texttt{parse\_expression}, ale pro násobení a~dělení. Volá \texttt{parse\_unary}.
\item \texttt{parse\_unary} zjistí, zda je dalším tokenem unární operátor. V~případě, že se jedná o~mínus vytvoří uzel pro negaci,
jehož operandem se stane návratová hodnota \texttt{parse\_power}, jinak je tato funkce zavolána přímo.
\item \texttt{parse\_power} nejprve zavolá \texttt{parse\_factor} a~poté zjistí, zda následuje operátor umocnění. V~případě, že ano, vytvoří uzel pro umocnění,
jehož levým operandem je návratová hodnota \texttt{parse\_factor} a~pro zpracování pravého operandu zavolá znovu \texttt{parse\_unary}, jelikož
exponent může opět začínat unárním operátorem.
\item \texttt{parse\_factor} se rozhoduje na základě typu tokenu:
\begin{itemize}
\item v případě konstanty vytvoří uzel konstanty,
\item v případě proměnné vytvoří uzel proměnné,
\item v případě funkce zavolá \texttt{parse\_function},
\item v případě levé závorky zavolá \texttt{parse\_bracketed}.
\end{itemize}
\item \texttt{parse\_bracketed} zkontroluje, že je aktuálním tokem levá závorka, zavolá \texttt{parse\_expression} a~zkontroluje, že následuje pravá závorka.
\item \texttt{parse\_function} ověří, že se za názevm funkce nachází levá závorka, zavolá \texttt{parse\_expression} pro každý argument (počet argumentů závisí na konkrétní funkci) a~zkontroluje, že následuje pravá závorka.
Vytvoří uzel funkce, jehož potomci jsou zpracované argumenty.
\end{itemize}
Ve shrnutí tedy analýza začíná ve funkci \texttt{parse\_expression} a~postupuje dolů podle pravidel gramatiky, ze které vychází.
\subsubsection{Struktura \texttt{parser}}
Struktura \texttt{parser} představuje stav syntaktického analyzátoru. Obsahuje
instanci lexikálního analyzátoru a chybový zásobník.
Inicializace této struktury není třeba, protože proběhne automaticky
při zahájení analýzy.
\subsubsection{Analýza}
Funkce
\begin{lstlisting}
int parser_parse_n(struct parser *parser,
const char *str, const char *variable_name,
struct expr_node **out_nodes, size_t n);
\end{lstlisting}
zpracuje \texttt{n} výrazů v~řetězci \texttt{str}.
Mezi jednotlivými výrazy nemusí být v případě jednoznačnosti žádný oddělovač, avšak je možné je oddělit čárkou (např.
pokud jeden výraz začíná unárním operátorem). \texttt{variable\_name} je název proměnné, která se ve vstupním řetězci může vyskytnout.
V případě úspěchu vrací \texttt{1} a do pole \texttt{out\_nodes} jsou uloženy ukazatele na kořeny stromů zpracovaných výrazů. V případě, že
při zpracování nastane chyba, funkce vrací \texttt{0} a obsah pole \texttt{out\_nodes} je nevalidní (všechny již alokované stromy jsou automaticky uvolněny).
Tato funkce není v této práci využita přímo. Důvod její existence je popsán v sekci~\ref{sec:upg}.
Namísto toho je využita funkce
\begin{lstlisting}
struct expr_node *parser_parse(struct parser *parser, const char *str, const char *variable_name);
\end{lstlisting}
která zjednodušuje její volání pro $n = 1$. Vrací ukazatel na kořen stromu zpracovaného výrazu, nebo \texttt{NULL} v~případě chyby.
Každý strom je nutné uvolnit pomocí \texttt{node\_free}.
\subsubsection{Chyba při zpracování}
Podobně jako u~lexikálního analyzátoru je v~případě, že nastane chyba při zpracování výrazu,
možné pomocí funkce
\begin{lstlisting}
enum error_code parser_get_error(const struct parser *parser);
\end{lstlisting}
získat chybový kód. Pro získání textové reprezentace chyby lze využít funkci
\begin{lstlisting}
const char *parser_get_error_text(const struct parser *parser);
\end{lstlisting}
\subsection{Strom výrazu --- modul \texttt{tree}}
\subsection{Vykreslení grafu --- modul \texttt{ps\_graph}}
Modul \texttt{ps\_graph} vykresluje graf funkce do souboru ve formátu PostScript.
Vykreslení je zahájeno funkcí
\begin{lstlisting}
void ps_generate_graph(FILE *file,
const struct expr_node *node,
const struct graph_range *range,
const char *function);
\end{lstlisting}
kde \texttt{file} je ukazatel na soubor, do kterého se má graf vykreslit, \texttt{node} je kořen stromu zadané funkce,
\texttt{range} je rozsah, ve kterém se má graf vykreslit, a~\texttt{function} je textový popis zadané funkce (pro titulek grafu).
Tato funkce vypíše do souboru \texttt{file} příkazy jazyka PostScript:
\begin{itemize}
\item Nastaví transformaci souřadnicového systému tak, aby byl graf vykreslen na vhodném místě na stránce.
\item Vypíše titulek grafu.
\item Pomocí funkce \texttt{generate\_grid} vykreslí mřížku včetně hodnot a~popisků os.
\item Nastaví ořezovou oblast pro vykreslení grafu.
\item Vykreslí samotný graf funkcí pomocí funkce \texttt{generate\_function}. Tato funkce vykreslí lomenou čáru
příkazy \texttt{moveto} a~\texttt{lineto}. Mezi dolní a~horní mezí rozsahu osy $x$ jsou v iteraci rovnoměrně interpolovány
hodnoty $x$, jejichž počet je určen konstantou \texttt{FUNCTION\_SEGMENTS}. První příkaz je vždy \texttt{moveto}.
Zadaná funkce je v každém bodě vyhodnocena --- pokud je v~daném bodě definována, je vykreslena čára pomocí příkazu \texttt{lineto}.
V~opačném případě čára vykreslena není a~je zajištěno, aby další příkaz byl opět \texttt{moveto}.
\item Vykreslí kolem grafu rámeček.
\end{itemize}
% upg
\subsection{Použití v~\texttt{KIV/UPG}}\label{sec:upg}
Moduly umožňující vyhodnocení funkcí byly využity i v~rámci semestrální práce z předmětu \texttt{KIV/UPG},
kde sloužily k~animaci elektrických nábojů v~čase.
Z tohoto důvodu se v~práci nachází některé nadbytečné funkce:
\begin{itemize}
\item Vypisování chyb do zásobníku \texttt{error\_buffer} namísto standardního výstupu, jelikož
v~\texttt{UPG} se nejednalo o~konzolovou aplikaci a chyby byly vypsány do grafického rozhraní.
\item Argument \texttt{variable\_name} ve~funkcích \texttt{lex\_init} a~\texttt{parser\_parse\_n}.
V~\texttt{UPG} byla pro soulad s tamním zadáním využita proměnná $t$ reprezentující čas.
\item Funkce \texttt{parser\_parse\_n}, která umožňuje zpracovat více výrazů v~jednom řetězci najednou. Byla
využita s~$n = 3$ ke zpracování vektorové funkce pro animaci pozice náboje.
\item Matematické funkce navíc, které byly využity v~některých scénářích.
\end{itemize}
\section{Uživatelská příručka}
V následujících podsekcích je popsáno, jak program sestavit a~používat.
\subsection{Kompilace}
Program je možné zkompilovat pomocí nástroje \texttt{make} a~přiloženého \texttt{Makefile}.
Tímto vznikne spustitelný soubor \texttt{graph.exe}.
\subsection{Použití}
Program se spouští příkazem \texttt{graph.exe} s~následujícími argumenty:
\begin{itemize}
\item Předpis funkce, která má být vykreslena.
\item Název souboru, do kterého se má graf uložit.
\item Volitelně: Rozsah grafu ve tvaru $x_d\texttt{:}x_h\texttt{:}y_d\texttt{:}y_h$,
kde $x_d$ a~$x_h$ jsou dolní a~horní mez osy $x$ a~$y_d$ a~$y_h$ jsou dolní a~horní mez osy $y$.
V případě, že tento argument není uveden, použije se výchozí rozsah $x_d = y_d = -10$ a~$x_h = y_h = 10$.
\end{itemize}
Návratová hodnota programu odopovídá některému z chybových kódů uvedených v~tabulce~\ref{tab:errors}.
Při úspěšném vykreslení vznikne soubor se zadaným názvem, který bude obsahovat graf funkce ve formátu PostScript.
\section{Závěr}
Práce splňuje zadání a~je schopna vykreslit graf zadané funkce na zadaném intervalu.
Stále se v~ní však nachází mnoho nedostatků a~možných vylepšení, např.~možnost zpracování a zobrazení více funkcí v jednom grafu,
detekce nespojitostí a~přizpůsobení vzhledu a parametrů grafu uživateli.
\end{document}