Compare commits
5 Commits
01ee2257a4
...
d88234f394
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d88234f394 | ||
|
|
b713991e4b | ||
|
|
ddec1650e6 | ||
|
|
51a1db8539 | ||
|
|
82e865e0d4 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
.vs/
|
||||
.vscode/
|
||||
build/
|
||||
odevdz/
|
||||
*.cppcheck
|
||||
*.dot
|
||||
*.png
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
BIN = graph.exe
|
||||
OBJ = errors.o lex.o main.o math_functions.o parser.o ps_graph.o tree.o
|
||||
OBJ = errors.obj lex.obj main.obj math_functions.obj parser.obj ps_graph.obj tree.obj
|
||||
|
||||
.c.obj:
|
||||
cl $< /c
|
||||
|
||||
@ -145,13 +145,13 @@ Typy tokenů využité v~této práci jsou uvedeny v~tabulce~\ref{tab:tokens}.
|
||||
\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.
|
||||
V~této práci je použita metoda rekurzivního sestupu, která je relativně jednoduchá a~přehledná.
|
||||
|
||||
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.
|
||||
|
||||
\newpage
|
||||
|
||||
Pro zpracování matematických lze sestavit gramatiku
|
||||
|
||||
\begin{verbatim}
|
||||
@ -171,9 +171,9 @@ kde \verb|{}| značí iteraci, \verb|[]| volitelnost a~\texttt{|} jednu z~možno
|
||||
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.
|
||||
% 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
|
||||
@ -210,6 +210,8 @@ každého modulu je (až na výjimky) nezávislá na implementaci ostatních mod
|
||||
|
||||
Každý modul včetně jeho rozhraní je popsán v~následujících podsekcích.
|
||||
|
||||
\newpage
|
||||
|
||||
\subsection{Vstupní bod programu --- \texttt{main.c}}
|
||||
|
||||
Výkon programu začíná ve~funkci \texttt{main}, která má na starosti následující úkoly:
|
||||
@ -299,11 +301,29 @@ 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.
|
||||
\begin{lstlisting}
|
||||
struct expr_node *node_create_const(double val);
|
||||
struct expr_node *node_create_x(void);
|
||||
struct expr_node *node_create_add(
|
||||
struct expr_node *left, struct expr_node *right);
|
||||
struct expr_node *node_create_sub(
|
||||
struct expr_node *left, struct expr_node *right);
|
||||
struct expr_node *node_create_mult(
|
||||
struct expr_node *left, struct expr_node *right);
|
||||
struct expr_node *node_create_div(
|
||||
struct expr_node *left, struct expr_node *right);
|
||||
struct expr_node *node_create_pow(
|
||||
struct expr_node *base, struct expr_node *power);
|
||||
struct expr_node *node_create_neg(
|
||||
struct expr_node *unop);
|
||||
struct expr_node *node_create_fn(
|
||||
size_t fn_idx, struct expr_node **args);
|
||||
|
||||
\end{lstlisting}
|
||||
|
||||
Tyto funkce vrací dynamicky alokovaný uzel, který je třeba po použití uvolnit (v~případě, že se uzel stane potomkem jiného uzlu, je
|
||||
automaticky uvolněn s~tímto uzlem).
|
||||
|
||||
\subsubsection{Evaluace uzlu}
|
||||
Uzel lze vyhodnotit v~daném bodě pomocí funkce
|
||||
@ -312,8 +332,8 @@ Uzel lze vyhodnotit v~daném bodě pomocí funkce
|
||||
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}.
|
||||
která v~případě úspěchu vrací \texttt{EVAL\_OK} a~do \texttt{y} uloží výslednou 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
|
||||
@ -409,6 +429,8 @@ inicializuje lexikální analyzátor \texttt{lex} pro zpracování řetězce \te
|
||||
|
||||
Deinicializace není třeba, protože při inicializaci ani činnosti tohoto modulu není prováděna žádná alokace paměti.
|
||||
|
||||
\newpage
|
||||
|
||||
\subsubsection{Získávání tokenů}
|
||||
Aktuální token je možné získat pomocí funkce
|
||||
\begin{lstlisting}
|
||||
@ -416,7 +438,7 @@ Aktuální token je možné získat pomocí funkce
|
||||
const struct lexer *lex);
|
||||
\end{lstlisting}
|
||||
která na něj vrací ukazatel.
|
||||
Pro získání dalšího tokenu je třeba zavolat funkci
|
||||
Pro načtení dalšího tokenu je třeba zavolat funkci
|
||||
\begin{lstlisting}
|
||||
void lex_next(struct lexer *lex);
|
||||
\end{lstlisting}
|
||||
@ -451,7 +473,7 @@ 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.
|
||||
analýze a~vytváří strom zadaného výrazu.
|
||||
|
||||
\subsubsection{Postup analýzy}
|
||||
Syntantický analyzátor je implementován pomocí rekurzivního sestupu.
|
||||
@ -467,7 +489,7 @@ se skládá z~prefixu \texttt{parse\_} a~názvu neterminálu:
|
||||
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,
|
||||
\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ž
|
||||
@ -558,7 +580,7 @@ Tato funkce vypíše do souboru \texttt{file} příkazy jazyka PostScript:
|
||||
\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:
|
||||
Z tohoto důvodu se v~práci nachází některé \uv{nadbytečné} funkce:
|
||||
|
||||
\begin{itemize}
|
||||
\item Vypisování chyb do zásobníku \texttt{error\_buffer} namísto standardního výstupu, jelikož
|
||||
@ -580,13 +602,13 @@ 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}
|
||||
\begin{enumerate}
|
||||
\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$.
|
||||
kde $x_d$ a~$x_h$ jsou dolní a~horní meze osy $x$ a~$y_d$ a~$y_h$ jsou dolní a~horní meze 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}
|
||||
\end{enumerate}
|
||||
|
||||
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.
|
||||
|
||||
5
errors.c
5
errors.c
@ -19,19 +19,24 @@ enum error_code error_get(const struct error_buffer *eb) {
|
||||
|
||||
void error_printf(struct error_buffer *eb, const char *format, ...) {
|
||||
va_list args;
|
||||
/* volné místo v zásobníku */
|
||||
int space = MAX_ERROR_MESSAGE_LENGTH - eb->text_len;
|
||||
int write_size;
|
||||
|
||||
/* už není volné místo */
|
||||
if (space == 0)
|
||||
return;
|
||||
|
||||
/* zápis */
|
||||
va_start(args, format);
|
||||
write_size = vsnprintf(eb->text + eb->text_len, MAX_ERROR_MESSAGE_LENGTH - eb->text_len, format, args);
|
||||
va_end(args);
|
||||
|
||||
/* chyba */
|
||||
if (write_size < 0)
|
||||
return;
|
||||
|
||||
/* aktualizace délky */
|
||||
if (write_size < space) {
|
||||
eb->text_len += write_size;
|
||||
}
|
||||
|
||||
2
errors.h
2
errors.h
@ -1,4 +1,4 @@
|
||||
#ifndef ERRORS_H
|
||||
#ifndef ERRORS_H
|
||||
#define ERRORS_H
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
26
lex.c
26
lex.c
@ -1,4 +1,4 @@
|
||||
#include "lex.h"
|
||||
#include "lex.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
#include "math_functions.h"
|
||||
|
||||
/* konstanty */
|
||||
#define CONST_PI 3.141592653589793
|
||||
#define CONST_E 2.718281828459045
|
||||
#define CONST_S (1.0 / 9.0)
|
||||
@ -36,11 +37,14 @@ static void skip_whitespace(struct lexer *lex) {
|
||||
static int try_advance(struct lexer *lex, const char *str) {
|
||||
const char *temp_p = lex->p;
|
||||
while (1) {
|
||||
/* pokud jsme se dostali až na konec řetězce, vrátíme 1 */
|
||||
if (!*str) {
|
||||
lex->p = temp_p;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* pokud se někde neshoduje, vrátíme 0 */
|
||||
/* (je zahrnut případ, že str skončí dřív) */
|
||||
if (*str != *temp_p) {
|
||||
return 0;
|
||||
}
|
||||
@ -102,8 +106,14 @@ static int try_fns(struct lexer *lex) {
|
||||
static int try_number(struct lexer *lex) {
|
||||
char *end;
|
||||
double val;
|
||||
|
||||
/* pokus o přečtení čísla */
|
||||
val = strtod(lex->p, &end);
|
||||
|
||||
/* pokud se na aktuální pozici nenacházelo číslo, vrátíme 0 */
|
||||
if (lex->p == end) return 0;
|
||||
|
||||
/* nastavit token */
|
||||
lex->tok.type = TOK_NUMBER;
|
||||
lex->tok.val.num = val;
|
||||
lex->p = end;
|
||||
@ -121,37 +131,51 @@ static int try_constant(struct lexer *lex, const char *name, double val) {
|
||||
}
|
||||
|
||||
void lex_next(struct lexer *lex) {
|
||||
/* přeskočit bílé znaky */
|
||||
skip_whitespace(lex);
|
||||
|
||||
/* uložit pozici pro případné vypsání chyby */
|
||||
/* (v případě úspěšné tokenizace ale chyby syntaxe
|
||||
by p ukazovalo na další token)*/
|
||||
lex->prev_p = lex->p;
|
||||
|
||||
/* pokud jsme na konci řetězce, nastavit EOF */
|
||||
if (!*lex->p) {
|
||||
lex->tok.type = TOK_EOF;
|
||||
return;
|
||||
}
|
||||
|
||||
/* pokus o rozpoznání tokenů */
|
||||
/* operátory */
|
||||
if (try_token(lex, "+", TOK_PLUS)) return;
|
||||
if (try_token(lex, "-", TOK_MINUS)) return;
|
||||
if (try_token(lex, "*", TOK_MULTIPLY)) return;
|
||||
if (try_token(lex, "/", TOK_DIVIDE)) return;
|
||||
if (try_token(lex, "^", TOK_POWER)) return;
|
||||
|
||||
/* závorky a oddělovač */
|
||||
if (try_token(lex, "(", TOK_LEFT_PAREN)) return;
|
||||
if (try_token(lex, ")", TOK_RIGHT_PAREN)) return;
|
||||
if (try_token(lex, ",", TOK_COMMA)) return;
|
||||
|
||||
/* symbolické konstanty */
|
||||
if (try_constant(lex, "pi", CONST_PI)) return;
|
||||
if (try_constant(lex, "e", CONST_E)) return;
|
||||
if (try_constant(lex, "skibidi", CONST_S)) return; /* easter egg */
|
||||
|
||||
/* proměnná */
|
||||
if (try_advance_identifier(lex, lex->variable_name)) {
|
||||
lex->tok.type = TOK_VARIABLE;
|
||||
return;
|
||||
}
|
||||
|
||||
/* funkce */
|
||||
if (try_fns(lex)) return;
|
||||
|
||||
/* číselná konstanta */
|
||||
if (try_number(lex)) return;
|
||||
|
||||
/* možnosti vyčerpány => chyba */
|
||||
error_set(&lex->eb, ERR_INVALID_FUNCTION);
|
||||
error_printf(&lex->eb, "Unrecognized sequence \"%s\"\n", lex->p);
|
||||
lex_print_position(lex, &lex->eb);
|
||||
|
||||
4
lex.h
4
lex.h
@ -1,10 +1,8 @@
|
||||
#ifndef LEX_H
|
||||
#ifndef LEX_H
|
||||
#define LEX_H
|
||||
|
||||
#include "errors.h"
|
||||
|
||||
#define LEX_DEBUG
|
||||
|
||||
/**
|
||||
* @brief Typ tokenu
|
||||
*/
|
||||
|
||||
13
main.c
13
main.c
@ -13,13 +13,16 @@ static void print_usage(FILE *f, const char *name) {
|
||||
|
||||
}
|
||||
|
||||
/* přečte z řetězce číslo a posune ukazatel */
|
||||
static int read_double(const char **str, double *out) {
|
||||
char *end;
|
||||
*out = strtod(*str, &end);
|
||||
|
||||
/* pokud se na aktuální pozici nenacházelo číslo, vrátíme 0 */
|
||||
if (*str == end)
|
||||
return 0;
|
||||
|
||||
/* posuneme ukazatel za číslo */
|
||||
*str = end;
|
||||
return 1;
|
||||
}
|
||||
@ -32,6 +35,7 @@ static int read_double(const char **str, double *out) {
|
||||
if (*str++ != c) \
|
||||
return 0;
|
||||
|
||||
/* Zpracuje rozsah vykreslení grafu ve formátu "xmin:xmax:ymin:ymax" */
|
||||
static int parse_range(const char *str, struct graph_range *range) {
|
||||
EXPECT_DOUBLE(str, &range->xmin);
|
||||
EXPECT_CHAR(str, ':');
|
||||
@ -44,6 +48,7 @@ static int parse_range(const char *str, struct graph_range *range) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* vygeneruje graf funkce do souboru */
|
||||
static enum error_code export_to_file(const struct expr_node *node, const struct graph_range *range, const char *function, const char *out_name) {
|
||||
FILE *file;
|
||||
|
||||
@ -59,6 +64,7 @@ static enum error_code export_to_file(const struct expr_node *node, const struct
|
||||
}
|
||||
|
||||
#ifdef ENABLE_GRAPHVIZ_EXPORT
|
||||
/* vygeneruje strom výrazu v .dot formátu pro ladění */
|
||||
static void export_gv(const struct expr_node *node, const char *out_name) {
|
||||
FILE *file;
|
||||
|
||||
@ -79,11 +85,13 @@ int main(int argc, char *argv[]) {
|
||||
struct expr_node *node;
|
||||
struct graph_range range;
|
||||
|
||||
/* kontrola argumentů */
|
||||
if (argc < 3) {
|
||||
print_usage(stderr, argv[0]);
|
||||
return ERR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
/* zpracování rozsahu */
|
||||
if (argc > 3) {
|
||||
if (!parse_range(argv[3], &range)) {
|
||||
fprintf(stderr, "Invalid format of ranges! The correct format is \"xMin:xMax:yMin:yMax\".\n");
|
||||
@ -97,20 +105,25 @@ int main(int argc, char *argv[]) {
|
||||
range.ymax = 10.0;
|
||||
}
|
||||
|
||||
/* zpracování výrazu */
|
||||
node = parser_parse(&parser, argv[1], "x");
|
||||
|
||||
if (!node) {
|
||||
/* vypsání chyby */
|
||||
fprintf(stderr, "%s", parser_get_error_text(&parser));
|
||||
return parser_get_error(&parser);
|
||||
}
|
||||
|
||||
/* vygenerování grafu */
|
||||
err = export_to_file(node, &range, argv[1], argv[2]);
|
||||
|
||||
#ifdef ENABLE_GRAPHVIZ_EXPORT
|
||||
export_gv(node, "graph.dot");
|
||||
#endif /* ENABLE_GRAPHVIZ_EXPORT */
|
||||
|
||||
/* uvolnění paměti */
|
||||
node_free(node);
|
||||
|
||||
/* návratový kód */
|
||||
return err;
|
||||
}
|
||||
@ -3,6 +3,7 @@
|
||||
#include <math.h>
|
||||
#include <errno.h>
|
||||
|
||||
/* Vytvoří vyhodnocovač matematické funkce s jedním argumentem */
|
||||
#define MAKE_FUNCTION_1_ARG(name) \
|
||||
static enum eval_result mf_##name(double *y, const double *args) { \
|
||||
errno = 0; \
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
#ifndef MATH_FUNCTIONS_H
|
||||
#ifndef MATH_FUNCTIONS_H
|
||||
#define MATH_FUNCTIONS_H
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
60
parser.c
60
parser.c
@ -48,8 +48,10 @@ static void error_expected_tokens(struct parser *parser, size_t num_tokens, ...)
|
||||
size_t i;
|
||||
va_list ap;
|
||||
|
||||
/* vyskytující se token */
|
||||
enum token_type got = get_token(parser)->type;
|
||||
|
||||
/* chyba při lexikální analýze */
|
||||
if (got == TOK_ERROR) {
|
||||
error_set(&parser->eb, lex_get_error(&parser->lexer));
|
||||
error_printf(&parser->eb, "%s", lex_get_error_text(&parser->lexer));
|
||||
@ -59,6 +61,7 @@ static void error_expected_tokens(struct parser *parser, size_t num_tokens, ...)
|
||||
error_set(&parser->eb, ERR_INVALID_FUNCTION);
|
||||
error_printf(&parser->eb, "Syntax error - expected ");
|
||||
|
||||
/* výpis očekávaných tokenů */
|
||||
va_start(ap, num_tokens);
|
||||
for (i = 0; i < num_tokens; ++i) {
|
||||
enum token_type tok = va_arg(ap, enum token_type);
|
||||
@ -70,8 +73,10 @@ static void error_expected_tokens(struct parser *parser, size_t num_tokens, ...)
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
/* výpis vyskytlého se tokenu */
|
||||
error_printf(&parser->eb, " - but got %s\n", lex_token_str(got));
|
||||
|
||||
/* výpis aktuální pozice ve vstupním řetězci */
|
||||
lex_print_position(&parser->lexer, &parser->eb);
|
||||
}
|
||||
|
||||
@ -82,14 +87,17 @@ static int parse_n_expressions(struct parser *parser, struct expr_node **out_nod
|
||||
static struct expr_node *parse_bracketed(struct parser *parser) {
|
||||
struct expr_node *node;
|
||||
|
||||
/* požadovat levou závorku */
|
||||
if (!accept_token(parser, TOK_LEFT_PAREN)) {
|
||||
error_expected_tokens(parser, 1, TOK_LEFT_PAREN);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* zpracovat výraz v závorkach */
|
||||
if (!(node = parse_expression(parser)))
|
||||
return NULL;
|
||||
|
||||
/* požadovat pravou závorku */
|
||||
if (!accept_token(parser, TOK_RIGHT_PAREN)) {
|
||||
error_expected_tokens(parser, 1, TOK_RIGHT_PAREN);
|
||||
node_free(node);
|
||||
@ -103,28 +111,35 @@ 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;
|
||||
|
||||
/* funkce */
|
||||
size_t fn_idx = token_fn_idx(parser);
|
||||
const struct math_function *fn = &fns_get()[fn_idx];
|
||||
|
||||
next_token(parser);
|
||||
|
||||
/* požadovat levou závorku */
|
||||
if (!accept_token(parser, TOK_LEFT_PAREN)) {
|
||||
error_expected_tokens(parser, 1, TOK_LEFT_PAREN);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* zpracovat argumenty funkce */
|
||||
if (!parse_n_expressions(parser, arg_nodes, fn->num_args))
|
||||
return NULL;
|
||||
|
||||
/* vytvořit uzel funkce */
|
||||
if (!(node = node_create_fn(fn_idx, arg_nodes))) {
|
||||
error_bad_alloc(parser);
|
||||
|
||||
/* uvolnit všechny uzly argumentů */
|
||||
for (i = 0; i < fn->num_args; ++i)
|
||||
node_free(arg_nodes[i]);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* požadovat pravou závorku */
|
||||
if (!accept_token(parser, TOK_RIGHT_PAREN)) {
|
||||
error_expected_tokens(parser, 1, TOK_RIGHT_PAREN);
|
||||
node_free(node);
|
||||
@ -138,6 +153,7 @@ static struct expr_node *parse_function(struct parser *parser) {
|
||||
static struct expr_node *parse_factor(struct parser *parser) {
|
||||
struct expr_node *node;
|
||||
|
||||
/* konstanta */
|
||||
if (token_is(parser, TOK_NUMBER)) {
|
||||
double val = token_num(parser);
|
||||
next_token(parser);
|
||||
@ -149,6 +165,7 @@ static struct expr_node *parse_factor(struct parser *parser) {
|
||||
return node;
|
||||
}
|
||||
|
||||
/* proměnná */
|
||||
if (accept_token(parser, TOK_VARIABLE)) {
|
||||
if (!(node = node_create_x())) {
|
||||
error_bad_alloc(parser);
|
||||
@ -157,10 +174,12 @@ static struct expr_node *parse_factor(struct parser *parser) {
|
||||
return node;
|
||||
}
|
||||
|
||||
/* funkce */
|
||||
if (token_is(parser, TOK_FUNCTION)) {
|
||||
return parse_function(parser);
|
||||
}
|
||||
|
||||
/* výraz obalený závorkami */
|
||||
if (token_is(parser, TOK_LEFT_PAREN)) {
|
||||
return parse_bracketed(parser);
|
||||
}
|
||||
@ -175,15 +194,19 @@ static struct expr_node *parse_unary(struct parser *parser);
|
||||
static struct expr_node *parse_power(struct parser *parser) {
|
||||
struct expr_node *node, *new_node, *inner;
|
||||
|
||||
/* základ (nebo výsledek pokud nenásleduje umocnění) */
|
||||
if (!(node = parse_factor(parser)))
|
||||
return NULL;
|
||||
|
||||
/* umocnění? */
|
||||
if (accept_token(parser, TOK_POWER)) {
|
||||
/* exponent */
|
||||
if (!(inner = parse_unary(parser))) {
|
||||
node_free(node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* vytvořit uzel umocnění */
|
||||
if (!(new_node = node_create_pow(node, inner))) {
|
||||
error_bad_alloc(parser);
|
||||
node_free(node);
|
||||
@ -199,12 +222,15 @@ static struct expr_node *parse_power(struct parser *parser) {
|
||||
|
||||
/* Zpracuje unární mínus, resp. plus */
|
||||
static struct expr_node *parse_unary(struct parser *parser) {
|
||||
/* unární mínus? */
|
||||
if (accept_token(parser, TOK_MINUS)) {
|
||||
struct expr_node *node, *inner;
|
||||
|
||||
/* operand */
|
||||
if (!(inner = parse_power(parser)))
|
||||
return NULL;
|
||||
|
||||
/* vytvořit uzel negace */
|
||||
if (!(node = node_create_neg(inner))) {
|
||||
error_bad_alloc(parser);
|
||||
node_free(inner);
|
||||
@ -214,7 +240,9 @@ static struct expr_node *parse_unary(struct parser *parser) {
|
||||
return node;
|
||||
}
|
||||
|
||||
/* unární plus - nehraje roli */
|
||||
accept_token(parser, TOK_PLUS);
|
||||
|
||||
return parse_power(parser);
|
||||
}
|
||||
|
||||
@ -222,12 +250,17 @@ static struct expr_node *parse_unary(struct parser *parser) {
|
||||
static struct expr_node *parse_term(struct parser *parser) {
|
||||
struct expr_node *node, *new_node, *inner;
|
||||
|
||||
/* výraz před prvním operátorem */
|
||||
if (!(node = parse_unary(parser)))
|
||||
return NULL;
|
||||
|
||||
/* iterovat dokud se za výrazy nachází operátory součinu nebo dělení */
|
||||
/* bylo by možné "vyřešit" rekurzí, ale není nutné */
|
||||
while (1) {
|
||||
/* funkce pro vytvoření uzlu binární operace na základě operátoru */
|
||||
struct expr_node *(*create_node)(struct expr_node *left, struct expr_node *right);
|
||||
|
||||
/* rozhodnout se na základě operátoru, případně ukončit smyčku */
|
||||
if (accept_token(parser, TOK_MULTIPLY))
|
||||
create_node = node_create_mult;
|
||||
else if (accept_token(parser, TOK_DIVIDE))
|
||||
@ -235,11 +268,13 @@ static struct expr_node *parse_term(struct parser *parser) {
|
||||
else
|
||||
break;
|
||||
|
||||
/* zpracovat další výraz */
|
||||
if (!(inner = parse_unary(parser))) {
|
||||
node_free(node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* vytvořit uzel pro danou operaci */
|
||||
if (!(new_node = create_node(node, inner))) {
|
||||
error_bad_alloc(parser);
|
||||
node_free(node);
|
||||
@ -247,6 +282,7 @@ static struct expr_node *parse_term(struct parser *parser) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* uzly postupně řetězit */
|
||||
node = new_node;
|
||||
}
|
||||
|
||||
@ -257,12 +293,17 @@ static struct expr_node *parse_term(struct parser *parser) {
|
||||
static struct expr_node *parse_expression(struct parser *parser) {
|
||||
struct expr_node *node, *new_node, *inner;
|
||||
|
||||
/* výraz před prvním operátorem */
|
||||
if (!(node = parse_term(parser)))
|
||||
return NULL;
|
||||
|
||||
/* iterovat dokud se za výrazy nachází operátory sčítání nebo odčítání */
|
||||
/* bylo by možné "vyřešit" rekurzí, ale není nutné */
|
||||
while (1) {
|
||||
/* funkce pro vytvoření uzlu binární operace na základě operátoru */
|
||||
struct expr_node *(*create_node)(struct expr_node *left, struct expr_node *right);
|
||||
|
||||
/* rozhodnout se na základě operátoru, případně ukončit smyčku */
|
||||
if (accept_token(parser, TOK_PLUS))
|
||||
create_node = node_create_add;
|
||||
else if (accept_token(parser, TOK_MINUS))
|
||||
@ -270,11 +311,13 @@ static struct expr_node *parse_expression(struct parser *parser) {
|
||||
else
|
||||
break;
|
||||
|
||||
/* zpracovat další výraz */
|
||||
if (!(inner = parse_term(parser))) {
|
||||
node_free(node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* vytvořit uzel pro danou operaci */
|
||||
if (!(new_node = create_node(node, inner))) {
|
||||
error_bad_alloc(parser);
|
||||
node_free(node);
|
||||
@ -282,6 +325,7 @@ static struct expr_node *parse_expression(struct parser *parser) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* uzly postupně řetězit */
|
||||
node = new_node;
|
||||
}
|
||||
|
||||
@ -299,49 +343,65 @@ static int parse_n_expressions(struct parser *parser, struct expr_node **out_nod
|
||||
|
||||
out_nodes[i] = node;
|
||||
|
||||
/* akceptovat čárku jakožto oddělovač výrazů */
|
||||
if (i < n - 1)
|
||||
accept_token(parser, TOK_COMMA);
|
||||
}
|
||||
|
||||
/* nebyly zpracovány všechny výrazy? */
|
||||
if (i != n) {
|
||||
/* uvolnit všechny doposud alokované uzly */
|
||||
while (i) {
|
||||
--i;
|
||||
node_free(out_nodes[i]);
|
||||
out_nodes[i] = NULL;
|
||||
}
|
||||
|
||||
/* chyba */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* úspěch */
|
||||
return 1;
|
||||
}
|
||||
|
||||
int parser_parse_n(struct parser *parser, const char *str, const char *variable_name, struct expr_node **out_nodes, size_t n) {
|
||||
/* inicializace chybového bufferu */
|
||||
error_buffer_init(&parser->eb);
|
||||
|
||||
/* inicializace lexikálního analyzázoru */
|
||||
lex_init(&parser->lexer, str, variable_name);
|
||||
|
||||
/* zpracování n tokenů */
|
||||
if (!parse_n_expressions(parser, out_nodes, n)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* požadovat konec vstupu */
|
||||
if (!token_is(parser, TOK_EOF)) {
|
||||
size_t i;
|
||||
error_expected_tokens(parser, 1, TOK_EOF);
|
||||
|
||||
/* uvolnit všechny uzly */
|
||||
for (i = 0; i < n; ++i) {
|
||||
node_free(out_nodes[i]);
|
||||
out_nodes[i] = NULL;
|
||||
}
|
||||
|
||||
/* chyba */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* úspěch */
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct expr_node *parser_parse(struct parser *parser, const char *str, const char *variable_name) {
|
||||
struct expr_node *out_node = NULL;
|
||||
|
||||
/* zpracování jednoho výrazu */
|
||||
parser_parse_n(parser, str, variable_name, &out_node, 1);
|
||||
|
||||
return out_node;
|
||||
}
|
||||
|
||||
|
||||
2
parser.h
2
parser.h
@ -1,4 +1,4 @@
|
||||
#ifndef PARSER_H
|
||||
#ifndef PARSER_H
|
||||
#define PARSER_H
|
||||
|
||||
#include "tree.h"
|
||||
|
||||
@ -11,4 +11,4 @@ struct graph_range {
|
||||
|
||||
void ps_generate_graph(FILE *file, const struct expr_node *node, const struct graph_range *range, const char *function);
|
||||
|
||||
#endif
|
||||
#endif /* PS_GRAPH_H */
|
||||
|
||||
7
tree.c
7
tree.c
@ -4,6 +4,7 @@
|
||||
#include <errno.h>
|
||||
#include "tree.h"
|
||||
|
||||
/* alokuje nový uzel */
|
||||
static struct expr_node *alloc_node(void) {
|
||||
return malloc(sizeof(struct expr_node));
|
||||
}
|
||||
@ -24,6 +25,7 @@ struct expr_node *node_create_neg(struct expr_node *unop) {
|
||||
return node;
|
||||
}
|
||||
|
||||
/* vytvoří uzel binární operace */
|
||||
static struct expr_node *create_binary_node(enum expr_type type, struct expr_node *left, struct expr_node *right) {
|
||||
struct expr_node *node = alloc_node();
|
||||
if (!node) return NULL;
|
||||
@ -75,12 +77,15 @@ struct expr_node *node_create_fn(size_t fn_idx, struct expr_node **args) {
|
||||
return node;
|
||||
}
|
||||
|
||||
/* pro naše účely definované "nekonečno" */
|
||||
#define INF (1.0e256)
|
||||
|
||||
/* zkontroluje, zda je číslo reálné */
|
||||
static int is_real(double x) {
|
||||
return x == x && x < INF && x > -INF;
|
||||
}
|
||||
|
||||
/* vrátí výsledek vyhodnocení na základě hodnoty x */
|
||||
static enum eval_result check_real(double x) {
|
||||
if (is_real(x))
|
||||
return EVAL_OK;
|
||||
@ -88,8 +93,6 @@ static enum eval_result check_real(double x) {
|
||||
return EVAL_ERROR;
|
||||
}
|
||||
|
||||
#define RETURN_CHECK_REAL(x) if (is_real((x))) return EVAL_OK; else return EVAL_ERROR;
|
||||
|
||||
#define EVAL_CHECK(node, x, y) if (node_eval((node), (x), (y)) != EVAL_OK) return EVAL_ERROR;
|
||||
|
||||
enum eval_result node_eval(const struct expr_node *node, double x, double *y) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user