% Specifikace třídy dokumentu a základní velikosti písma. \documentclass[12pt, a4paper]{article} % Podpora češtiny \usepackage[utf8]{inputenc} \usepackage[IL2]{fontenc} \usepackage[czech]{babel} \usepackage{amsfonts} % Okraje stránky \usepackage[ left=30mm, right=30mm, top=40mm, bottom=30mm, % twoside, % Při oboustranné sazbě si zkuste nastavit right=25mm a left=35mm. % showframe % Vykreslí okraje stránky. ]{geometry} % Americký styl odstavců (mně se tento styl líbí o poznání více) \usepackage{parskip} % Sazba obrázků \usepackage{graphicx} \graphicspath{{Images/}} % Při vkládání obrázků se bude prefixovat tato relativní cesta. % \usepackage{zi4} % \usepackage{courier} % 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. % `hidelinks` skryje ošklivé výchozí rámečky kolem odkazů. \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 \begin{document} % Titulní strana (prostředí minimálně odstraní číslo strany) \begin{titlepage} \centering % Odtud do konce prostředí bude vše na středu, \Large % velkými písmeny \sffamily % a bezpatkovým písmem. %Vložení obrázku (ze složky `Images`) \includegraphics[width=.7\textwidth]{fav} Semestrální práce z předmětu % Prázdná mezera mezi řádky znamená nový odstavec. Programování v jazyce C % Vertikální mezera 18 mm. \vspace{18mm} {\Huge\bfseries Vizualizace grafu matematické funkce}\\ \vspace{18mm} \today % Čas je získán ze systému. \vfill % Vyplní prostor \raggedright % Vše bude zarováno do leva. \textsl{Autor:}\\ % Vtípek z přednášky + ukázka tvorby makra a přidání sémantiky do stylu textu. Zbyněk Vajchart\\ % Příkaz \\ provede násilný zlom řádky. A23B0144P\\ \texttt{zbyv@students.zcu.cz} \end{titlepage} \tableofcontents \newpage % Ukázka odstranění čísla stránky -- první stránka obsahu ale má být číslovaná! %\thispagestyle{empty} % Sazba nové kapitoly (ve vašem případě zde bude zřejmě zkrácená verze "Zadání") \section{Zadání} Naprogramujte v jazyce ANSI C přenositelnou konzolovou aplikaci, která jako vstup načte z~parametru na příkazové řádce matematickou funkci ve tvaru $y = f(x); x, y \in \mathbb{R}$, provede její analýzu a~vytvoří soubor ve formátu PostScript s~grafem této funkce na 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}. \section{Analýza úlohy} 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 k její evaluaci a~vykreslení grafu. \subsection{Analýza funkce} Analýzu zadané matematické funkce je vhodné rozdělit do dvou částí --- analýzu lexikální a~syntaktickou. Výhoda tohoto rozdělení je popsána níže. \subsubsection{Lexikální analýza} Během lexikální analýzy je funkce rozdělena na jednotlivé, dále nedělitelné \textit{tokeny} (např. konstanty, operátory, proměnné). Některé typy tokenů pak mají hodnotu, typicky třeba konstanty --- jejich hodnotou je číslo, které reprezentují. Výhodou lexikální analýzy je, že zjednodušuje další zpracování funkce, protože z~pohledu syntaxe je jedno, zda se v~zápisu funkce vyskytne např.~\texttt{2}, \texttt{1e10} nebo \texttt{pi} --- všechny tyto sekvence znaků se při lexikální analýze převedou na token \textit{konstanta} s~odpovídající hodnotou. To samozřejmě platí i~pro jiné typy tokenů. Během lexikální analýzy jsou také odstraněny bílé znaky, které nemají v~zápisu funkce žádný význam. Typy tokenů využité v~této práci jsou uvedeny v~tabulce~\ref{tab:tokens}. \begin{table}[] \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 \end{tabular} \end{table} \subsubsection{Syntaktická analýza} 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é 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. 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 této gramatiky. Pro zpracování matematických lze sestavit gramatiku \begin{verbatim} = { ( PLUS | MINUS ) } = { ( MULTIPLY | DIVIDE ) } = [ PLUS | MINUS ] = [ POWER ] = NUMBER | VARIABLE | | = LEFT_PAREN RIGHT_PAREN = FUNCTION LEFT_PAREN { [ COMMA ] } RIGHT_PAREN \end{verbatim} kde \verb|{}| značí iteraci, \verb|[]| volitelnost a~\texttt{|} jednu z~možností. Výrazy v~\verb|<>| jsou neterminály, zatímco ostatní symboly jsou terminály odpovídající 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}