Compare commits

...

12 Commits

Author SHA1 Message Date
936c846b0c doku 3 2024-12-30 19:09:19 +01:00
8d587bf4d1 komentare k tree.h 2024-12-30 19:09:13 +01:00
1fb0614af4 komentare k ps_graph 2024-12-30 19:09:04 +01:00
d0b5599c8f konst, parse_function zvlast 2024-12-30 19:08:52 +01:00
b5d2e5c506 konst 2024-12-30 19:08:36 +01:00
5235189966 doku 2 2024-12-28 14:31:14 +01:00
6678a45c7d error_buffer -> errors 2024-12-28 14:31:05 +01:00
tovjemam
3774c3a9d2 doku 1 2024-12-25 15:54:34 +01:00
tovjemam
aa5b8ac41c hvězdičky jinam 2024-12-25 15:54:28 +01:00
tovjemam
6bd390736c komentare parseru 2024-12-25 12:07:47 +01:00
tovjemam
242bcb851d zmenit nazvy 2024-12-25 12:01:02 +01:00
tovjemam
71cfd1d0e5 format 2024-12-25 12:00:48 +01:00
12 changed files with 798 additions and 160 deletions

7
.gitignore vendored
View File

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

View File

@ -15,7 +15,7 @@ add_executable(Graph
"parser.c"
"tree.c"
"ps_graph.c"
"error_buffer.c"
"errors.c"
"math_functions.c"
)
@ -24,10 +24,12 @@ if (MSVC)
target_compile_options(Graph PRIVATE /W4)
else()
target_compile_options(Graph PRIVATE -Wall -Wextra -pedantic)
target_link_libraries(Graph PRIVATE m)
endif()
target_compile_definitions(Graph PRIVATE ENABLE_GRAPHVIZ_EXPORT)
# link math
target_link_libraries(Graph PRIVATE m)
# Optionally, set the output directory for the executable
# set_target_properties(Graph PROPERTIES

599
dokumentace/PC_graph.tex Normal file
View File

@ -0,0 +1,599 @@
% 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{bookmark}
\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}
\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é
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}
<expression> = <term> { ( PLUS | MINUS ) <term> }
<term> = <unary> { ( MULTIPLY | DIVIDE ) <unary> }
<unary> = [ PLUS | MINUS ] <power>
<power> = <factor> [ POWER <unary> ]
<factor> = NUMBER | VARIABLE | <function> | <bracketed>
<bracketed> = LEFT_PAREN <expression> RIGHT_PAREN
<function> = FUNCTION
LEFT_PAREN
<expression> { [ COMMA ] <expression> }
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 (až na výjimky) nezávislá na implementaci ostatních modulů.
Každý modul včetně jeho rozhraní je popsán v~následujících podsekcích.
\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{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.
\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}
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
\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}}
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{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}

View File

@ -1,4 +1,4 @@
#include "error_buffer.h"
#include "errors.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
@ -34,7 +34,8 @@ void error_printf(struct error_buffer *eb, const char *format, ...) {
if (write_size < space) {
eb->text_len += write_size;
} else {
}
else {
eb->text_len = MAX_ERROR_MESSAGE_LENGTH;
}
}

View File

@ -1,5 +1,5 @@
#ifndef ERROR_CODE_H
#define ERROR_CODE_H
#ifndef ERRORS_H
#define ERRORS_H
#include <stdlib.h>
@ -64,4 +64,4 @@ enum error_code error_get(const struct error_buffer *eb);
*/
const char *error_get_text(const struct error_buffer *eb);
#endif /* ERROR_CODE_H */
#endif /* ERRORS_H */

2
lex.c
View File

@ -160,7 +160,7 @@ void lex_next(struct lexer *lex) {
return;
}
struct token *lex_token(struct lexer *lex) {
const struct token *lex_token(const struct lexer *lex) {
return &lex->tok;
}

4
lex.h
View File

@ -1,7 +1,7 @@
#ifndef LEX_H
#define LEX_H
#include "error_buffer.h"
#include "errors.h"
#define LEX_DEBUG
@ -76,7 +76,7 @@ void lex_next(struct lexer *lex);
* @param lex Lexer
* @return Adresa na token
*/
struct token *lex_token(struct lexer *lex);
const struct token *lex_token(const struct lexer *lex);
/**
* @brief Vypíše informaci o aktuální pozici ve vstupním řetězci

2
main.c
View File

@ -3,7 +3,7 @@
#include "lex.h"
#include "parser.h"
#include "ps_graph.h"
#include "error_buffer.h"
#include "errors.h"
static void print_usage(FILE *f, const char *name) {
fprintf(f, "Usage: %s <function> <output file> [<range>]\n", name);

View File

@ -4,22 +4,22 @@
#include "parser.h"
/* Vrátí ukazatel na aktuální token */
static struct token *get_token(struct parser *parser) {
static const struct token *get_token(const struct parser *parser) {
return lex_token(&parser->lexer);
}
/* Vrátí 1, pokud aktuální token je typu <type> */
static int token_is(struct parser *parser, enum token_type type) {
static int token_is(const struct parser *parser, enum token_type type) {
return get_token(parser)->type == type;
}
/* Vrátí hodnotu tokenu, který je konstanta */
static double token_num(struct parser *parser) {
static double token_num(const struct parser *parser) {
return get_token(parser)->val.num;
}
/* Vrátí index funkce tokenu, který reprezentuje funkci */
static size_t token_fn_idx(struct parser *parser) {
static size_t token_fn_idx(const struct parser *parser) {
return get_token(parser)->val.fn_idx;
}
@ -43,6 +43,7 @@ static void error_bad_alloc(struct parser *parser) {
error_printf(&parser->eb, "Out of memory\n");
}
/* Vyhodí chybu při neočekávaném tokenu */
static void error_expected_tokens(struct parser *parser, size_t num_tokens, ...) {
size_t i;
va_list ap;
@ -51,7 +52,7 @@ static void error_expected_tokens(struct parser *parser, size_t num_tokens, ...)
if (got == TOK_ERROR) {
error_set(&parser->eb, lex_get_error(&parser->lexer));
error_printf(&parser->eb, "Lexer error - %s", lex_get_error_text(&parser->lexer));
error_printf(&parser->eb, "%s", lex_get_error_text(&parser->lexer));
return;
}
@ -74,18 +75,19 @@ static void error_expected_tokens(struct parser *parser, size_t num_tokens, ...)
lex_print_position(&parser->lexer, &parser->eb);
}
static struct expr_node* parse_subexpression(struct parser *parser);
int parse_n_expressions(struct parser *parser, struct expr_node **out_nodes, size_t n);
static struct expr_node *parse_expression(struct parser *parser);
static int parse_n_expressions(struct parser *parser, struct expr_node **out_nodes, size_t n);
static struct expr_node* parse_bracketed(struct parser *parser) {
struct expr_node* node;
/* Zpracuje výraz obalený závorkami */
static struct expr_node *parse_bracketed(struct parser *parser) {
struct expr_node *node;
if (!accept_token(parser, TOK_LEFT_PAREN)) {
error_expected_tokens(parser, 1, TOK_LEFT_PAREN);
return NULL;
}
if (!(node = parse_subexpression(parser)))
if (!(node = parse_expression(parser)))
return NULL;
if (!accept_token(parser, TOK_RIGHT_PAREN)) {
@ -97,29 +99,8 @@ static struct expr_node* parse_bracketed(struct parser *parser) {
return node;
}
static struct expr_node *parse_base(struct parser *parser) {
struct expr_node *node;
if (token_is(parser, TOK_NUMBER)) {
double val = token_num(parser);
next_token(parser);
if (!(node = node_create_const(val))) {
error_bad_alloc(parser);
return NULL;
}
return node;
}
if (accept_token(parser, TOK_VARIABLE)) {
if (!(node = node_create_x())) {
error_bad_alloc(parser);
return NULL;
}
return node;
}
if (token_is(parser, TOK_FUNCTION)) {
static struct expr_node *parse_function(struct parser *parser) {
struct expr_node *node;
struct expr_node *arg_nodes[MAX_MATH_FUNCTION_ARGS];
size_t i;
size_t fn_idx = token_fn_idx(parser);
@ -151,6 +132,33 @@ static struct expr_node *parse_base(struct parser *parser) {
}
return node;
}
/* Zpracuje část výrazu, která už má nejvyšší precedenci (číselnou konstantu, proměnnou, funkci, nebo výraz obalený závorkami) */
static struct expr_node *parse_factor(struct parser *parser) {
struct expr_node *node;
if (token_is(parser, TOK_NUMBER)) {
double val = token_num(parser);
next_token(parser);
if (!(node = node_create_const(val))) {
error_bad_alloc(parser);
return NULL;
}
return node;
}
if (accept_token(parser, TOK_VARIABLE)) {
if (!(node = node_create_x())) {
error_bad_alloc(parser);
return NULL;
}
return node;
}
if (token_is(parser, TOK_FUNCTION)) {
return parse_function(parser);
}
if (token_is(parser, TOK_LEFT_PAREN)) {
@ -163,10 +171,11 @@ static struct expr_node *parse_base(struct parser *parser) {
static struct expr_node *parse_unary(struct parser *parser);
static struct expr_node *parse_factor(struct parser *parser) {
struct expr_node* node, * new_node, * inner;
/* Zpracuje umocnění */
static struct expr_node *parse_power(struct parser *parser) {
struct expr_node *node, *new_node, *inner;
if (!(node = parse_base(parser)))
if (!(node = parse_factor(parser)))
return NULL;
if (accept_token(parser, TOK_POWER)) {
@ -188,11 +197,12 @@ static struct expr_node *parse_factor(struct parser *parser) {
return node;
}
static struct expr_node* parse_unary(struct parser *parser) {
/* Zpracuje unární mínus, resp. plus */
static struct expr_node *parse_unary(struct parser *parser) {
if (accept_token(parser, TOK_MINUS)) {
struct expr_node *node, *inner;
if (!(inner = parse_factor(parser)))
if (!(inner = parse_power(parser)))
return NULL;
if (!(node = node_create_neg(inner))) {
@ -205,9 +215,10 @@ static struct expr_node* parse_unary(struct parser *parser) {
}
accept_token(parser, TOK_PLUS);
return parse_factor(parser);
return parse_power(parser);
}
/* Zpracuje součin nebo dělení */
static struct expr_node *parse_term(struct parser *parser) {
struct expr_node *node, *new_node, *inner;
@ -242,7 +253,8 @@ static struct expr_node *parse_term(struct parser *parser) {
return node;
}
static struct expr_node *parse_subexpression(struct parser *parser) {
/* Zpracuje sčítání nebo odečítání */
static struct expr_node *parse_expression(struct parser *parser) {
struct expr_node *node, *new_node, *inner;
if (!(node = parse_term(parser)))
@ -276,12 +288,13 @@ static struct expr_node *parse_subexpression(struct parser *parser) {
return node;
}
int parse_n_expressions(struct parser *parser, struct expr_node **out_nodes, size_t n) {
/* Zpracuje n výrazů */
static int parse_n_expressions(struct parser *parser, struct expr_node **out_nodes, size_t n) {
size_t i;
for (i = 0; i < n; ++i) {
struct expr_node *node;
if (!(node = parse_subexpression(parser)))
if (!(node = parse_expression(parser)))
break;
out_nodes[i] = node;

View File

@ -109,6 +109,7 @@ static void generate_grid(FILE *file, const struct graph_range *range) {
}
/* popisky os */
set_font(file, FONT, FONT_SIZE_LABEL);
generate_text_align(file, -HALF_GRAPH_SIZE + Y_LABEL_OFFSET_X, Y_LABEL_OFFSET_Y, ALIGN_RIGHT, "y");
generate_text_align(file, X_LABEL_OFFSET_X, -HALF_GRAPH_SIZE + X_LABEL_OFFSET_Y, ALIGN_CENTER, "x");
@ -131,6 +132,7 @@ static void generate_function(FILE *file, const struct expr_node *node, const st
x = range->xmin + (range->xmax - range->xmin) * (double)i / (double)FUNCTION_SEGMENTS;
/* funkce není definována */
if (node_eval(node, x, &y) != EVAL_OK) {
first = 1;
continue;
@ -155,8 +157,10 @@ static void generate_function(FILE *file, const struct expr_node *node, const st
void ps_generate_graph(FILE *file, const struct expr_node *node, const struct graph_range *range, const char *function) {
char buf[TEMP_BUF_SIZE];
/* posun na střed */
fprintf(file, "%d %d translate\n", GRAPH_CENTER_X, GRAPH_CENTER_Y);
/* hlavička */
if (function) {
set_font(file, FONT, FONT_SIZE_HEADER);
snprintf(buf, TEMP_BUF_SIZE, "y = %s", function);
@ -165,6 +169,7 @@ void ps_generate_graph(FILE *file, const struct expr_node *node, const struct gr
generate_grid(file, range);
/* ořezová oblast pro graf */
fprintf(file,
"[] 0 setdash\n"
"newpath\n"
@ -178,8 +183,10 @@ void ps_generate_graph(FILE *file, const struct expr_node *node, const struct gr
-HALF_GRAPH_SIZE, -HALF_GRAPH_SIZE, GRAPH_SIZE, GRAPH_SIZE, -GRAPH_SIZE
);
/* samotný graf */
generate_function(file, node, range);
/* rámeček */
fprintf(file,
"grestore\n"
"2 setlinewidth\n"

View File

@ -9,6 +9,6 @@ struct graph_range {
double ymin, ymax;
};
extern void ps_generate_graph(FILE *file, const struct expr_node *node, const struct graph_range *range, const char *function);
void ps_generate_graph(FILE *file, const struct expr_node *node, const struct graph_range *range, const char *function);
#endif

9
tree.h
View File

@ -24,16 +24,25 @@ enum expr_type {
*/
struct expr_node {
enum expr_type type;
/* hodnoty dle typu uzlu */
union expr_vals {
/* operandy u binární operace */
struct expr_binop_vals {
struct expr_node *left;
struct expr_node *right;
} binop;
/* argumenty funkce */
struct expr_fn_vals {
size_t fn_idx;
struct expr_node *args[MAX_MATH_FUNCTION_ARGS];
} fn;
/* unární operand */
struct expr_node *unop;
/* hodnota konstanty */
double num;
} vals;
};