From 936c846b0cd5bb25badc495bc211e308ddb8efa8 Mon Sep 17 00:00:00 2001 From: det-fys Date: Mon, 30 Dec 2024 19:09:19 +0100 Subject: [PATCH] doku 3 --- dokumentace/PC_graph.tex | 331 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 310 insertions(+), 21 deletions(-) diff --git a/dokumentace/PC_graph.tex b/dokumentace/PC_graph.tex index cea8471..e17009e 100644 --- a/dokumentace/PC_graph.tex +++ b/dokumentace/PC_graph.tex @@ -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}