Compare commits
12 Commits
49598cfda3
...
936c846b0c
| Author | SHA1 | Date | |
|---|---|---|---|
| 936c846b0c | |||
| 8d587bf4d1 | |||
| 1fb0614af4 | |||
| d0b5599c8f | |||
| b5d2e5c506 | |||
| 5235189966 | |||
| 6678a45c7d | |||
|
|
3774c3a9d2 | ||
|
|
aa5b8ac41c | ||
|
|
6bd390736c | ||
|
|
242bcb851d | ||
|
|
71cfd1d0e5 |
7
.gitignore
vendored
7
.gitignore
vendored
@ -7,3 +7,10 @@ build/
|
||||
*.svg
|
||||
*.ps
|
||||
*.pdf
|
||||
*.aux
|
||||
*.fdb_latexmk
|
||||
*.fls
|
||||
*.log
|
||||
*.out
|
||||
*.synctex.gz
|
||||
*.toc
|
||||
|
||||
@ -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
599
dokumentace/PC_graph.tex
Normal 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}
|
||||
@ -1,44 +1,45 @@
|
||||
#include "error_buffer.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
void error_buffer_init(struct error_buffer *eb) {
|
||||
eb->err = ERR_NO_ERR;
|
||||
eb->text_len = 0;
|
||||
eb->text[0] = 0;
|
||||
}
|
||||
|
||||
void error_set(struct error_buffer *eb, enum error_code err) {
|
||||
eb->err = err;
|
||||
}
|
||||
|
||||
enum error_code error_get(const struct error_buffer *eb) {
|
||||
return eb->err;
|
||||
}
|
||||
|
||||
void error_printf(struct error_buffer *eb, const char *format, ...) {
|
||||
va_list args;
|
||||
int space = MAX_ERROR_MESSAGE_LENGTH - eb->text_len;
|
||||
int write_size;
|
||||
|
||||
if (space == 0)
|
||||
return;
|
||||
|
||||
va_start(args, format);
|
||||
write_size = vsnprintf(eb->text + eb->text_len, MAX_ERROR_MESSAGE_LENGTH - eb->text_len, format, args);
|
||||
va_end(args);
|
||||
|
||||
if (write_size < 0)
|
||||
return;
|
||||
|
||||
if (write_size < space) {
|
||||
eb->text_len += write_size;
|
||||
} else {
|
||||
eb->text_len = MAX_ERROR_MESSAGE_LENGTH;
|
||||
}
|
||||
}
|
||||
|
||||
const char *error_get_text(const struct error_buffer *eb) {
|
||||
return eb->text;
|
||||
#include "errors.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
void error_buffer_init(struct error_buffer *eb) {
|
||||
eb->err = ERR_NO_ERR;
|
||||
eb->text_len = 0;
|
||||
eb->text[0] = 0;
|
||||
}
|
||||
|
||||
void error_set(struct error_buffer *eb, enum error_code err) {
|
||||
eb->err = err;
|
||||
}
|
||||
|
||||
enum error_code error_get(const struct error_buffer *eb) {
|
||||
return eb->err;
|
||||
}
|
||||
|
||||
void error_printf(struct error_buffer *eb, const char *format, ...) {
|
||||
va_list args;
|
||||
int space = MAX_ERROR_MESSAGE_LENGTH - eb->text_len;
|
||||
int write_size;
|
||||
|
||||
if (space == 0)
|
||||
return;
|
||||
|
||||
va_start(args, format);
|
||||
write_size = vsnprintf(eb->text + eb->text_len, MAX_ERROR_MESSAGE_LENGTH - eb->text_len, format, args);
|
||||
va_end(args);
|
||||
|
||||
if (write_size < 0)
|
||||
return;
|
||||
|
||||
if (write_size < space) {
|
||||
eb->text_len += write_size;
|
||||
}
|
||||
else {
|
||||
eb->text_len = MAX_ERROR_MESSAGE_LENGTH;
|
||||
}
|
||||
}
|
||||
|
||||
const char *error_get_text(const struct error_buffer *eb) {
|
||||
return eb->text;
|
||||
}
|
||||
@ -1,67 +1,67 @@
|
||||
#ifndef ERROR_CODE_H
|
||||
#define ERROR_CODE_H
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#define MAX_ERROR_MESSAGE_LENGTH 512
|
||||
|
||||
/**
|
||||
* @brief Chybové kódy
|
||||
*/
|
||||
enum error_code {
|
||||
ERR_NO_ERR = 0, /* Žádná chyba */
|
||||
ERR_INVALID_ARGS = 1, /* Neplatné argumenty programu */
|
||||
ERR_INVALID_FUNCTION = 2, /* Zadaná matematická funkce je neplatná */
|
||||
ERR_INVALID_FILENAME = 3, /* Zadaný název souboru není platný */
|
||||
ERR_INVALID_LIMITS = 4, /* Zadané hranice jsou ve špatném formátu */
|
||||
ERR_BAD_ALLOC = 5 /* Při alokaci paměti nastala chyba */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Zásobník pro chybový kód a řetězec popisující chybu
|
||||
*/
|
||||
struct error_buffer {
|
||||
enum error_code err;
|
||||
char text[MAX_ERROR_MESSAGE_LENGTH];
|
||||
size_t text_len;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Inicializuje zásobník
|
||||
*
|
||||
* @param eb Zásobník
|
||||
*/
|
||||
void error_buffer_init(struct error_buffer *eb);
|
||||
|
||||
/**
|
||||
* @brief Nastaví chybový kód
|
||||
*
|
||||
* @param eb Zásobník
|
||||
* @param err Chybový kód
|
||||
*/
|
||||
void error_set(struct error_buffer *eb, enum error_code err);
|
||||
|
||||
/**
|
||||
* @brief Přidá do zásobníku formátovaný řetězec
|
||||
*
|
||||
* @param eb Zásobník
|
||||
*/
|
||||
void error_printf(struct error_buffer *eb, const char *format, ...);
|
||||
|
||||
/**
|
||||
* Vrátí chybový kód
|
||||
*
|
||||
* @param eb Zásobník
|
||||
* @return Chybový kód
|
||||
*/
|
||||
enum error_code error_get(const struct error_buffer *eb);
|
||||
|
||||
/**
|
||||
* Vrátí řetězec popisující chybu
|
||||
*
|
||||
* @param eb Zásobník
|
||||
* @return Řetězec popisující chybu
|
||||
*/
|
||||
const char *error_get_text(const struct error_buffer *eb);
|
||||
|
||||
#endif /* ERROR_CODE_H */
|
||||
#ifndef ERRORS_H
|
||||
#define ERRORS_H
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#define MAX_ERROR_MESSAGE_LENGTH 512
|
||||
|
||||
/**
|
||||
* @brief Chybové kódy
|
||||
*/
|
||||
enum error_code {
|
||||
ERR_NO_ERR = 0, /* Žádná chyba */
|
||||
ERR_INVALID_ARGS = 1, /* Neplatné argumenty programu */
|
||||
ERR_INVALID_FUNCTION = 2, /* Zadaná matematická funkce je neplatná */
|
||||
ERR_INVALID_FILENAME = 3, /* Zadaný název souboru není platný */
|
||||
ERR_INVALID_LIMITS = 4, /* Zadané hranice jsou ve špatném formátu */
|
||||
ERR_BAD_ALLOC = 5 /* Při alokaci paměti nastala chyba */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Zásobník pro chybový kód a řetězec popisující chybu
|
||||
*/
|
||||
struct error_buffer {
|
||||
enum error_code err;
|
||||
char text[MAX_ERROR_MESSAGE_LENGTH];
|
||||
size_t text_len;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Inicializuje zásobník
|
||||
*
|
||||
* @param eb Zásobník
|
||||
*/
|
||||
void error_buffer_init(struct error_buffer *eb);
|
||||
|
||||
/**
|
||||
* @brief Nastaví chybový kód
|
||||
*
|
||||
* @param eb Zásobník
|
||||
* @param err Chybový kód
|
||||
*/
|
||||
void error_set(struct error_buffer *eb, enum error_code err);
|
||||
|
||||
/**
|
||||
* @brief Přidá do zásobníku formátovaný řetězec
|
||||
*
|
||||
* @param eb Zásobník
|
||||
*/
|
||||
void error_printf(struct error_buffer *eb, const char *format, ...);
|
||||
|
||||
/**
|
||||
* Vrátí chybový kód
|
||||
*
|
||||
* @param eb Zásobník
|
||||
* @return Chybový kód
|
||||
*/
|
||||
enum error_code error_get(const struct error_buffer *eb);
|
||||
|
||||
/**
|
||||
* Vrátí řetězec popisující chybu
|
||||
*
|
||||
* @param eb Zásobník
|
||||
* @return Řetězec popisující chybu
|
||||
*/
|
||||
const char *error_get_text(const struct error_buffer *eb);
|
||||
|
||||
#endif /* ERRORS_H */
|
||||
2
lex.c
2
lex.c
@ -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;
|
||||
}
|
||||
|
||||
|
||||
6
lex.h
6
lex.h
@ -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
|
||||
@ -114,4 +114,4 @@ const char *lex_get_error_text(const struct lexer *lex);
|
||||
*/
|
||||
const char *lex_token_str(enum token_type token);
|
||||
|
||||
#endif /* LEX_H */
|
||||
#endif /* LEX_H */
|
||||
|
||||
2
main.c
2
main.c
@ -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);
|
||||
|
||||
97
parser.c
97
parser.c
@ -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;
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
9
tree.h
@ -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;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user