From c8dd221b6747749a41c2bfbde363b7c244de8583 Mon Sep 17 00:00:00 2001 From: zbyv Date: Thu, 16 Jan 2025 13:55:12 +0100 Subject: [PATCH] ej --- .gitignore | 3 + CMakeLists.txt | 28 +++ CMakeUserPresets.json | 29 +++ canvas_graph.cpp | 96 ++++++++++ canvas_graph.h | 14 ++ errors.c | 50 +++++ errors.h | 67 +++++++ lex.c | 238 ++++++++++++++++++++++++ lex.h | 115 ++++++++++++ main.cpp | 58 ++++++ math_functions.c | 100 ++++++++++ math_functions.h | 41 +++++ parser.c | 414 ++++++++++++++++++++++++++++++++++++++++++ parser.h | 54 ++++++ shell.html | 102 +++++++++++ tree.c | 281 ++++++++++++++++++++++++++++ tree.h | 151 +++++++++++++++ 17 files changed, 1841 insertions(+) create mode 100644 .gitignore create mode 100755 CMakeLists.txt create mode 100644 CMakeUserPresets.json create mode 100755 canvas_graph.cpp create mode 100755 canvas_graph.h create mode 100755 errors.c create mode 100755 errors.h create mode 100755 lex.c create mode 100755 lex.h create mode 100644 main.cpp create mode 100755 math_functions.c create mode 100755 math_functions.h create mode 100755 parser.c create mode 100755 parser.h create mode 100644 shell.html create mode 100755 tree.c create mode 100755 tree.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5d28f84 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/ +emsdk/ +.vscode/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..da1552d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.21) +project(GraphWeb) +add_executable(GraphWeb + "main.cpp" + "lex.c" + "parser.c" + "tree.c" + "canvas_graph.cpp" + "errors.c" + "math_functions.c" +) +if (CMAKE_SYSTEM_NAME STREQUAL Emscripten) + set(CMAKE_EXECUTABLE_SUFFIX .html) + + # Specify the custom HTML shell + # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --shell-file ${CMAKE_SOURCE_DIR}/shell.html") + # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --shell-file ${CMAKE_SOURCE_DIR}/shell.html") + + # target_compile_options(GraphWeb PRIVATE --shell-file ${CMAKE_SOURCE_DIR}/shell.html) + # target_link_options(GraphWeb PRIVATE ) + set_target_properties(GraphWeb PROPERTIES LINK_FLAGS "--bind -sEXPORTED_FUNCTIONS='[\"_main\", \"_draw_graph\"]' -sEXPORTED_RUNTIME_METHODS='[\"cwrap\", \"ccall\"]' --shell-file ${CMAKE_SOURCE_DIR}/shell.html") + # target_link_options(GraphWeb PRIVATE -) + + +endif() + + +target_link_libraries(GraphWeb PRIVATE m) diff --git a/CMakeUserPresets.json b/CMakeUserPresets.json new file mode 100644 index 0000000..fd54b27 --- /dev/null +++ b/CMakeUserPresets.json @@ -0,0 +1,29 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 21, + "patch": 0 + }, + "configurePresets": [ + { + "name": "default", + "displayName": "Emscripten", + "binaryDir": "build", + "generator": "Ninja Multi-Config", + "toolchainFile": "emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" + } + ], + "buildPresets": [ + { + "name": "Debug", + "configurePreset": "default", + "configuration": "Debug" + }, + { + "name": "Release", + "configurePreset": "default", + "configuration": "Release" + } + ] +} \ No newline at end of file diff --git a/canvas_graph.cpp b/canvas_graph.cpp new file mode 100755 index 0000000..9116b68 --- /dev/null +++ b/canvas_graph.cpp @@ -0,0 +1,96 @@ +extern "C" { +#include "canvas_graph.h" +} + +#include + +using namespace emscripten; + +void canvas_generate_graph(const struct expr_node *node, const struct graph_range *range, const char *function) { + const val document = val::global("document"); + val temp = document.call("getElementById", val("temp")); + + std::string out; + + for (int i = -10; i <= 10; ++i) { + double x = i * 0.1; + double y; + eval_result res = node_eval(node, x, &y); + + if (res == EVAL_OK) { + out += std::to_string(y); + out += "\n"; + } + } + + temp.set("innerText", out); + + val canvas = document.call("getElementById", val("canvas")); + val ctx = canvas.call("getContext", val("2d")); + double canvas_width = canvas["width"].as(); + double canvas_height = canvas["height"].as(); + + // clear canvas + ctx.call("clearRect", val(0), val(0), val(canvas_width), val(canvas_height)); + + // grid + double big_range = std::max(range->xmax - range->xmin, range->ymax - range->ymin); + double grid_size = std::pow((int)std::log10(big_range), 10.0); + + if (grid_size < 0.1) + grid_size = 0.1; + + ctx.set("strokeStyle", val("#888888")); + ctx.set("lineWidth", val(1.0)); + ctx.call("beginPath"); + + double first_grid_x = (int)(range->xmin / grid_size) * grid_size; + double grid_x; + for (int i = 0; (grid_x = first_grid_x + i * grid_size) <= range->xmax; ++i) { + double x_canvas = (grid_x - range->xmin) / (range->xmax - range->xmin) * canvas_width; + ctx.call("moveTo", val(x_canvas), val(0)); + ctx.call("lineTo", val(x_canvas), val(canvas_height)); + } + + double first_grid_y = (int)(range->ymin / grid_size) * grid_size; + double grid_y; + for (int i = 0; (grid_y = first_grid_y + i * grid_size) <= range->ymax; ++i) { + double y_canvas = (range->ymax - grid_y) / (range->ymax - range->ymin) * canvas_height; + ctx.call("moveTo", val(0), val(y_canvas)); + ctx.call("lineTo", val(canvas_width), val(y_canvas)); + } + + + ctx.call("stroke"); + + // graph + ctx.set("strokeStyle", val("#000088")); + ctx.set("lineWidth", val(2.0)); + ctx.call("beginPath"); + double segment_size = (range->xmax - range->xmin) / 1000.0; + double first_x = (int)(range->xmin / segment_size) * segment_size; + double x; + bool use_moveto = true; + for (int i = 0; (x = first_x + i * segment_size) <= range->xmax; ++i) { + double y; + eval_result res = node_eval(node, x, &y); + + if (res != EVAL_OK) { + use_moveto = true; + continue; + } + + double x_canvas = (x - range->xmin) / (range->xmax - range->xmin) * canvas_width; + double y_canvas = (range->ymax - y) / (range->ymax - range->ymin) * canvas_height; + + const char* cmd = "lineTo"; + if (use_moveto) { + cmd = "moveTo"; + use_moveto = false; + } + + ctx.call(cmd, val(x_canvas), val(y_canvas)); + } + + ctx.call("stroke"); +} \ No newline at end of file diff --git a/canvas_graph.h b/canvas_graph.h new file mode 100755 index 0000000..10ddc75 --- /dev/null +++ b/canvas_graph.h @@ -0,0 +1,14 @@ +#ifndef CANVAS_GRAPH_H +#define CANVAS_GRAPH_H + +#include +#include "tree.h" + +struct graph_range { + double xmin, xmax; + double ymin, ymax; +}; + +void canvas_generate_graph(const struct expr_node *node, const struct graph_range *range, const char *function); + +#endif /* CANVAS_GRAPH_H */ diff --git a/errors.c b/errors.c new file mode 100755 index 0000000..fdefb1c --- /dev/null +++ b/errors.c @@ -0,0 +1,50 @@ +#include "errors.h" +#include +#include +#include + +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; + /* 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; + } + else { + eb->text_len = MAX_ERROR_MESSAGE_LENGTH; + } +} + +const char *error_get_text(const struct error_buffer *eb) { + return eb->text; +} \ No newline at end of file diff --git a/errors.h b/errors.h new file mode 100755 index 0000000..3ff8dcb --- /dev/null +++ b/errors.h @@ -0,0 +1,67 @@ +#ifndef ERRORS_H +#define ERRORS_H + +#include + +#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 */ \ No newline at end of file diff --git a/lex.c b/lex.c new file mode 100755 index 0000000..fb6034b --- /dev/null +++ b/lex.c @@ -0,0 +1,238 @@ +#include "lex.h" + +#include +#include +#include + +#include "math_functions.h" + +/* konstanty */ +#define CONST_PI 3.141592653589793 +#define CONST_E 2.718281828459045 +#define CONST_S (1.0 / 9.0) + +void lex_init(struct lexer *lex, const char *str, const char *variable_name) { + error_buffer_init(&lex->eb); + + lex->start = str; + lex->prev_p = str; + lex->p = str; + lex->variable_name = variable_name; + lex_next(lex); +} + +/* Vrátí 1, pokud je c "bílý znak" */ +static int is_whitespace(char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} + +/* Přeskočí bílé znaky v řetězci */ +static void skip_whitespace(struct lexer *lex) { + while (is_whitespace(*lex->p)) + ++lex->p; +} + +/* Posune ukazatel, pokud se v řetězci na aktuální pozici nachází + řetězec str a vrátí 1, jinak ukazatel nezmění a vrátí 0 */ +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; + } + + ++temp_p; + ++str; + } +} + +/* Vrátí 1, pokud se c smí vyskytovat v identifikátorech */ +static int is_identifier_char(char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +/* Stejné jako try_advance, ale přidává podmínku, že následující znak + není znakem s povoleným výskytem v identifikátorech. + Např. "sin" bude rozpoznán v "sin(...)", ale v "sinh" ne. +*/ +static int try_advance_identifier(struct lexer *lex, const char *str) { + const char *temp_p = lex->p; + if (!try_advance(lex, str)) + return 0; + + /* ověřit konec identifikátoru */ + if (is_identifier_char(*lex->p)) { + lex->p = temp_p; + return 0; + } + + return 1; +} + +/* Vrátí 1, pokud je na začátku řetězce nalezen daný token a nastaví příslušně aktuální token. */ +static int try_token(struct lexer *lex, const char *tok_str, enum token_type type) { + if (try_advance(lex, tok_str)) { + lex->tok.type = type; + return 1; + } + return 0; +} + +/* Vrátí 1, pokud je na začátku řetězce nalezena daná funkce a nastaví příslušně aktuální token. */ +static int try_fns(struct lexer *lex) { + const struct math_function *const fns = fns_get(); + size_t i; + + for (i = 0; fns[i].name; ++i) { + if (try_advance_identifier(lex, fns[i].name)) { + lex->tok.type = TOK_FUNCTION; + lex->tok.val.fn_idx = i; + return 1; + } + } + + return 0; +} + +/* Vrátí 1, pokud je na začátku řetězce nalezena číselná konstanta a nastaví příslušně aktuální token. */ +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; + return 1; +} + +/* Vrátí 1, pokud je na začátku řetězce nalezena speciální číselná konstanta a nastaví příslušně aktuální token. */ +static int try_constant(struct lexer *lex, const char *name, double val) { + if (!try_advance_identifier(lex, name)) + return 0; + + lex->tok.type = TOK_NUMBER; + lex->tok.val.num = val; + return 1; +} + +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); + + lex->tok.type = TOK_ERROR; + return; +} + +const struct token *lex_token(const struct lexer *lex) { + return &lex->tok; +} + +void lex_print_position(const struct lexer *lex, struct error_buffer *eb) { + size_t pos = lex->prev_p - lex->start; + size_t i; + size_t input_len = strlen(lex->start); + + error_printf(eb, "At character %d\n", pos); + error_printf(eb, " %s\n", lex->start); + error_printf(eb, " "); + + for (i = 0; i < input_len; ++i) { + char c = '~'; + if (i == pos) + c = '^'; + + error_printf(eb, "%c", c); + } + + error_printf(eb, "\n"); +} + +enum error_code lex_get_error(const struct lexer *lex) { + return error_get(&lex->eb); +} + +const char *lex_get_error_text(const struct lexer *lex) { + return error_get_text(&lex->eb); +} + +const char *lex_token_str(enum token_type token) { + switch (token) + { + case TOK_EOF: return ""; + case TOK_ERROR: return ""; + case TOK_NUMBER: return ""; + case TOK_PLUS: return "'+'"; + case TOK_MINUS: return "'-'"; + case TOK_MULTIPLY: return "'*'"; + case TOK_DIVIDE: return "'/'"; + case TOK_POWER: return "'^'"; + case TOK_VARIABLE: return ""; + case TOK_FUNCTION: return ""; + case TOK_LEFT_PAREN: return "'('"; + case TOK_RIGHT_PAREN: return "')'"; + case TOK_COMMA: return "','"; + + default: return ""; + } +} diff --git a/lex.h b/lex.h new file mode 100755 index 0000000..fbc2575 --- /dev/null +++ b/lex.h @@ -0,0 +1,115 @@ +#ifndef LEX_H +#define LEX_H + +#include "errors.h" + +/** + * @brief Typ tokenu + */ +enum token_type { + TOK_EOF, + TOK_ERROR, + + TOK_NUMBER, + TOK_PLUS, + TOK_MINUS, + TOK_MULTIPLY, + TOK_DIVIDE, + TOK_POWER, + + TOK_VARIABLE, + TOK_FUNCTION, + + TOK_LEFT_PAREN, + TOK_RIGHT_PAREN, + TOK_COMMA +}; + +/** + * @brief Token + * + * Struktura popisující konkrétní token, který se vyskytnul při lexikální analýze funkce. + */ +struct token { + enum token_type type; + union token_val { + double num; + size_t fn_idx; + } val; +}; + +/** + * @brief Lexikální analyzátor (lexer) + * + * Struktura uchovávající stav lexikálního analyzátoru. + */ +struct lexer { + const char *start; + const char *prev_p; + const char *p; + struct token tok; + struct error_buffer eb; + const char *variable_name; +}; + +/** + * @brief Inicializuje lexikální analyzátor + * + * @param lex Lexer + * @param str Řetězec pro analýzu + * @param variable_name Název proměnné + */ +void lex_init(struct lexer *lex, const char *str, const char *variable_name); + +/** + * @brief Načte další token + * + * @param lex Lexer + */ +void lex_next(struct lexer *lex); + +/** + * @brief Vrátí adresu na aktuální token + * + * @param lex Lexer + * @return Adresa na token + */ +const struct token *lex_token(const struct lexer *lex); + +/** + * @brief Vypíše informaci o aktuální pozici ve vstupním řetězci + * + * Tato funkce je volána, když při zpracování vstupní funkce nastane chyba. + * + * @param lex Lexer + * @param eb Zásobník, do kterého bude informace vypsána + */ +void lex_print_position(const struct lexer *lex, struct error_buffer *eb); + +/** + * @brief Vrátí kód chyby, která nastala při lexikální analýze + * + * @param lex Lexer + * @return Chybový kód + */ +enum error_code lex_get_error(const struct lexer *lex); + +/** + * @brief Vrátí textovou reprezentaci chyby, která nastala při lexikální analýze + * + * @param lex Lexer + * @return Řetězec s chybou + */ +const char *lex_get_error_text(const struct lexer *lex); + +/** + * @brief Vrátí textovou reprezentaci tokenu + * + * Tato funkce zohledńuje pouze typ tokenu, hodnota (např. číselná u konstanty) nebude v řetězci obsažena. + * + * @param token Token + * @return Textová reprezentace + */ +const char *lex_token_str(enum token_type token); + +#endif /* LEX_H */ diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..43995c6 --- /dev/null +++ b/main.cpp @@ -0,0 +1,58 @@ +#include +#include +#include + +extern "C" { +#include "lex.h" +#include "parser.h" +#include "canvas_graph.h" +} + +static void draw_graph_impl(struct error_buffer *eb, const char *expr, const struct graph_range *range) { + struct parser parser; + struct expr_node *node; + + error_buffer_init(eb); + + /* zpracování výrazu */ + node = parser_parse(&parser, expr, "x"); + + if (!node) { + /* vypsání chyby */ + error_set(eb, parser_get_error(&parser)); + error_printf(eb, "%s", parser_get_error_text(&parser)); + return; + } + + /* vygenerování grafu */ + canvas_generate_graph(node, range, expr); + + /* uvolnění paměti */ + node_free(node); + + return; +} + +int main() { + +} + +using namespace emscripten; + +extern "C" void draw_graph(const char *expr, double xmin, double xmax, double ymin, double ymax) { + const val document = val::global("document"); + val p = document.call("getElementById", val("output")); + + struct error_buffer eb; + struct graph_range range = {xmin, xmax, ymin, ymax}; + draw_graph_impl(&eb, expr, &range); + + if (error_get(&eb) != ERR_NO_ERR) { + p.set("innerText", error_get_text(&eb)); + } + else { + p.set("innerText", ""); + } + + +} diff --git a/math_functions.c b/math_functions.c new file mode 100755 index 0000000..ec62ce4 --- /dev/null +++ b/math_functions.c @@ -0,0 +1,100 @@ +#include "math_functions.h" + +#include +#include + +/* 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; \ + *y = name(args[0]); \ + if (errno) \ + return EVAL_ERROR; \ + return EVAL_OK; \ + } + +MAKE_FUNCTION_1_ARG(fabs) +MAKE_FUNCTION_1_ARG(exp) +MAKE_FUNCTION_1_ARG(log) +MAKE_FUNCTION_1_ARG(log10) +MAKE_FUNCTION_1_ARG(sin) +MAKE_FUNCTION_1_ARG(cos) +MAKE_FUNCTION_1_ARG(tan) +MAKE_FUNCTION_1_ARG(asin) +MAKE_FUNCTION_1_ARG(acos) +MAKE_FUNCTION_1_ARG(atan) +MAKE_FUNCTION_1_ARG(sinh) +MAKE_FUNCTION_1_ARG(cosh) +MAKE_FUNCTION_1_ARG(tanh) +MAKE_FUNCTION_1_ARG(floor) +MAKE_FUNCTION_1_ARG(ceil) + +static enum eval_result mf_sgn(double *y, const double *args) { + if (args[0] < 0.0) + *y = -1.0; + else if (args[0] > 0.0) + *y = 1.0; + else + *y = 0.0; + + return EVAL_OK; +} + +static enum eval_result mf_min(double *y, const double *args) { + if (args[0] < args[1]) + *y = args[0]; + else + *y = args[1]; + + return EVAL_OK; +} + +static enum eval_result mf_max(double *y, const double *args) { + if (args[0] > args[1]) + *y = args[0]; + else + *y = args[1]; + + return EVAL_OK; +} + +static enum eval_result mf_mod(double *y, const double *args) { + errno = 0; + *y = fmod(args[0], args[1]); + + if (errno) + return EVAL_ERROR; + + return EVAL_OK; +} + +const struct math_function *fns_get(void) { + static const struct math_function fns[] = { + /* +--- název + | +--- počet argumentů + | | +--- vyhodnocovač + | | | */ + { "abs", 1, mf_fabs }, + { "exp", 1, mf_exp }, + { "ln", 1, mf_log }, + { "log", 1, mf_log10 }, + { "sin", 1, mf_sin }, + { "cos", 1, mf_cos }, + { "tan", 1, mf_tan }, + { "asin", 1, mf_asin }, + { "acos", 1, mf_acos }, + { "atan", 1, mf_atan }, + { "sinh", 1, mf_sinh }, + { "cosh", 1, mf_cosh }, + { "tanh", 1, mf_tanh }, + { "min", 2, mf_min }, + { "max", 2, mf_max }, + { "mod", 2, mf_mod }, + { "sgn", 1, mf_sgn }, + { "floor", 1, mf_floor }, + { "ceil", 1, mf_ceil }, + { NULL } + }; + + return fns; +} diff --git a/math_functions.h b/math_functions.h new file mode 100755 index 0000000..7388046 --- /dev/null +++ b/math_functions.h @@ -0,0 +1,41 @@ +#ifndef MATH_FUNCTIONS_H +#define MATH_FUNCTIONS_H + +#include + +#define MAX_MATH_FUNCTION_ARGS 2 + +/** + * @brief Výsledek vyhodnocení výrazu + */ +enum eval_result { + EVAL_OK, + EVAL_ERROR +}; + +/** + * @brief Ukazatel na vyhodnocovač matematické funkce + */ +typedef enum eval_result (*math_function_ptr)(double* y, const double* args); + +/** + * @brief Matematická funkce + * + * Struktura popisující matematickou funkci, kterou lze použít ve vstupním výrazu + */ +struct math_function { + const char *name; + size_t num_args; + math_function_ptr ptr; +}; + +/** + * @brief Vrátí ukazatel na pole obsahující matematické funkce + * + * Toto pole je ukončené položkou s atributem name == NULL. + * + * @return Ukazatel na pole funkcí + */ +const struct math_function *fns_get(void); + +#endif /* MATH_FUNCTIONS_H */ \ No newline at end of file diff --git a/parser.c b/parser.c new file mode 100755 index 0000000..5c21ec2 --- /dev/null +++ b/parser.c @@ -0,0 +1,414 @@ +#include +#include +#include +#include "parser.h" + +/* Vrátí ukazatel na aktuální token */ +static const struct token *get_token(const struct parser *parser) { + return lex_token(&parser->lexer); +} + +/* Vrátí 1, pokud aktuální token je typu */ +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(const struct parser *parser) { + return get_token(parser)->val.num; +} + +/* Vrátí index funkce tokenu, který reprezentuje funkci */ +static size_t token_fn_idx(const struct parser *parser) { + return get_token(parser)->val.fn_idx; +} + +/* Načte další token */ +static void next_token(struct parser *parser) { + lex_next(&parser->lexer); +} + +/* Vrátí 1, pokud je aktuální token typu a načte další */ +static int accept_token(struct parser *parser, enum token_type type) { + if (token_is(parser, type)) { + next_token(parser); + return 1; + } + return 0; +} + +/* Nastaví chybu při alokaci */ +static void error_bad_alloc(struct parser *parser) { + error_set(&parser->eb, ERR_BAD_ALLOC); + 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; + + /* 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)); + return; + } + + 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); + + if (i > 0) + error_printf(&parser->eb, ", "); + + error_printf(&parser->eb, "%s", lex_token_str(tok)); + } + 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); +} + +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); + +/* Zpracuje výraz obalený závorkami */ +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); + return NULL; + } + + return node; +} + +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); + return NULL; + } + + 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; + + /* konstanta */ + 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; + } + + /* proměnná */ + if (accept_token(parser, TOK_VARIABLE)) { + if (!(node = node_create_x())) { + error_bad_alloc(parser); + return NULL; + } + 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); + } + + error_expected_tokens(parser, 4, TOK_NUMBER, TOK_VARIABLE, TOK_FUNCTION, TOK_LEFT_PAREN); + return NULL; +} + +static struct expr_node *parse_unary(struct parser *parser); + +/* Zpracuje umocnění */ +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); + node_free(inner); + return NULL; + } + + return new_node; + } + + return node; +} + +/* 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); + return NULL; + } + + return node; + } + + /* unární plus - nehraje roli */ + accept_token(parser, TOK_PLUS); + + 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; + + /* 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)) + create_node = node_create_div; + 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); + node_free(inner); + return NULL; + } + + /* uzly postupně řetězit */ + node = new_node; + } + + return node; +} + +/* Zpracuje sčítání nebo odečítání */ +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)) + create_node = node_create_sub; + 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); + node_free(inner); + return NULL; + } + + /* uzly postupně řetězit */ + node = new_node; + } + + return node; +} + +/* 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_expression(parser))) + break; + + 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; +} + +enum error_code parser_get_error(const struct parser *parser) { + return error_get(&parser->eb); +} + +const char *parser_get_error_text(const struct parser *parser) { + return error_get_text(&parser->eb); +} \ No newline at end of file diff --git a/parser.h b/parser.h new file mode 100755 index 0000000..9ae432a --- /dev/null +++ b/parser.h @@ -0,0 +1,54 @@ +#ifndef PARSER_H +#define PARSER_H + +#include "tree.h" +#include "lex.h" + +/** + * @brief Syntaktický analyzátor (parser) + */ +struct parser { + struct lexer lexer; + struct error_buffer eb; +}; + +/** + * @brief Zpracuje n výrazů ze vstupního řetězce + * + * @param parser Parser + * @param str Vstupní řetězec + * @param variable_name Název proměnné + * @param out_nodes Adresa na pole, kam budou adresy kořenových uzlů výrazů uloženy + * @param n Počet výrazů + * + * @return 1 při úspěchu, 0 při chybě + */ +int parser_parse_n(struct parser *parser, const char *str, const char *variable_name, struct expr_node **out_nodes, size_t n); + +/** + * @brief Zpracuje jeden výraz ze vstupního řetězce + * + * @param parser Parser + * @param str Vstupní řetězec + * @param variable_name Název proměnné + * @return Adresa kořenového uzlu výrazu + */ +struct expr_node *parser_parse(struct parser *parser, const char *str, const char *variable_name); + +/** + * @brief Vrátí kód chyby nastalé během syntaktické analýzy + * + * @param parser Parser + * @return Chybový kód + */ +enum error_code parser_get_error(const struct parser *parser); + +/** + * @brief Vrátí textovou reprezentaci chyby nastalé během syntaktické analýzy + * + * @param parser Parser + * @return Řetězec obsahující text chyby + */ +const char *parser_get_error_text(const struct parser *parser); + +#endif /* PARSER_H */ \ No newline at end of file diff --git a/shell.html b/shell.html new file mode 100644 index 0000000..6ca1b08 --- /dev/null +++ b/shell.html @@ -0,0 +1,102 @@ + + + + + + Document + + + + + + + + + +

+    

+    
+    
+
+    {{{ SCRIPT }}}
+
+
\ No newline at end of file
diff --git a/tree.c b/tree.c
new file mode 100755
index 0000000..0a3bda5
--- /dev/null
+++ b/tree.c
@@ -0,0 +1,281 @@
+#include 
+#include 
+#include 
+#include 
+#include "tree.h"
+
+/* alokuje nový uzel */
+static struct expr_node *alloc_node(void) {
+    return malloc(sizeof(struct expr_node));
+}
+
+struct expr_node *node_create_const(double val) {
+    struct expr_node *node = alloc_node();
+    if (!node) return NULL;
+    node->type = EXPR_CONST;
+    node->vals.num = val;
+    return node;
+}
+
+struct expr_node *node_create_neg(struct expr_node *unop) {
+    struct expr_node *node = alloc_node();
+    if (!node) return NULL;
+    node->type = EXPR_NEG;
+    node->vals.unop = 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;
+    node->type = type;
+    node->vals.binop.left = left;
+    node->vals.binop.right = right;
+    return node;
+}
+
+struct expr_node *node_create_add(struct expr_node *left, struct expr_node *right) {
+    return create_binary_node(EXPR_ADD, left, right);
+}
+
+struct expr_node *node_create_sub(struct expr_node *left, struct expr_node *right) {
+    return create_binary_node(EXPR_SUB, left, right);
+}
+
+struct expr_node *node_create_mult(struct expr_node *left, struct expr_node *right) {
+    return create_binary_node(EXPR_MULT, left, right);
+}
+
+struct expr_node *node_create_div(struct expr_node *left, struct expr_node *right) {
+    return create_binary_node(EXPR_DIV, left, right);
+}
+
+struct expr_node *node_create_pow(struct expr_node *base, struct expr_node *power) {
+    return create_binary_node(EXPR_POW, base, power);
+}
+
+struct expr_node *node_create_x(void) {
+    struct expr_node *node = alloc_node();
+    if (!node) return NULL;
+    node->type = EXPR_X;
+    return node;
+}
+
+struct expr_node *node_create_fn(size_t fn_idx, struct expr_node **args) {
+    size_t i, num_args;
+    struct expr_node *node = alloc_node();
+    if (!node) return NULL;
+    node->type = EXPR_FN;
+    node->vals.fn.fn_idx = fn_idx;
+
+    num_args = fns_get()[fn_idx].num_args;
+    for (i = 0; i < num_args; ++i) {
+        node->vals.fn.args[i] = args[i];
+    }
+
+    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;
+    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) {
+    double tmp1, tmp2;
+
+    switch (node->type) {
+        case EXPR_CONST:
+            *y = node->vals.num;
+            return check_real(*y);
+
+        case EXPR_X:
+            *y = x;
+            return check_real(*y);
+
+        case EXPR_NEG:
+            EVAL_CHECK(node->vals.unop, x, &tmp1);
+            *y = -tmp1;
+            return EVAL_OK;
+
+        case EXPR_ADD:
+            EVAL_CHECK(node->vals.binop.left, x, &tmp1);
+            EVAL_CHECK(node->vals.binop.right, x, &tmp2);
+            *y = tmp1 + tmp2;
+            return check_real(*y);
+
+        case EXPR_SUB:
+            EVAL_CHECK(node->vals.binop.left, x, &tmp1);
+            EVAL_CHECK(node->vals.binop.right, x, &tmp2);
+            *y = tmp1 - tmp2;
+            return check_real(*y);
+
+        case EXPR_MULT:
+            EVAL_CHECK(node->vals.binop.left, x, &tmp1);
+            EVAL_CHECK(node->vals.binop.right, x, &tmp2);
+            *y = tmp1 * tmp2;
+            return check_real(*y);
+
+        case EXPR_DIV:
+            EVAL_CHECK(node->vals.binop.left, x, &tmp1);
+            EVAL_CHECK(node->vals.binop.right, x, &tmp2);
+            
+            if (tmp2 == 0.0)
+                return EVAL_ERROR;
+            
+            *y = tmp1 / tmp2;
+            return check_real(*y);
+
+        case EXPR_POW:
+            EVAL_CHECK(node->vals.binop.left, x, &tmp1);
+            EVAL_CHECK(node->vals.binop.right, x, &tmp2);
+            
+            errno = 0;
+            *y = pow(tmp1, tmp2);
+            
+            if (errno) 
+                return EVAL_ERROR;
+            
+            return check_real(*y);
+
+        case EXPR_FN:
+        {
+            double inner_results[MAX_MATH_FUNCTION_ARGS];
+            size_t i;
+            const struct math_function *fn = &fns_get()[node->vals.fn.fn_idx];
+
+            for (i = 0; i < fn->num_args; ++i) {
+                EVAL_CHECK(node->vals.fn.args[i], x, &inner_results[i]);
+            }
+
+            return fn->ptr(y, inner_results);
+        }
+    }
+
+    return 0.0;
+}
+
+#ifdef ENABLE_GRAPHVIZ_EXPORT
+
+static void debug_print_gv(const struct expr_node *node, FILE *output);
+
+static void debug_print_binop_gv(const struct expr_node *node, FILE *output, const char *name) {
+    fprintf(output, "node%p [label=\"%s\"]\n", (void*)node, name);
+    debug_print_gv(node->vals.binop.left, output);
+    debug_print_gv(node->vals.binop.right, output);
+    fprintf(output, "node%p -> node%p [label=left]\n", (void*)node, (void*)node->vals.binop.left);
+    fprintf(output, "node%p -> node%p [label=right]\n", (void*)node, (void*)node->vals.binop.right);
+}
+
+static void debug_print_gv(const struct expr_node *node, FILE *output) {
+
+    switch (node->type) {
+        case EXPR_ADD:
+            debug_print_binop_gv(node, output, "ADD");
+            break;
+
+        case EXPR_SUB:
+            debug_print_binop_gv(node, output, "SUB");
+            break;
+
+        case EXPR_MULT:
+            debug_print_binop_gv(node, output, "MULT");
+            break;
+
+        case EXPR_DIV:
+            debug_print_binop_gv(node, output, "DIV");
+            break;
+
+        case EXPR_POW:
+            debug_print_binop_gv(node, output, "POW");
+            break;
+
+        case EXPR_NEG:
+            fprintf(output, "node%p [label=\"NEG\"]\n", (void*)node);
+            debug_print_gv(node->vals.unop, output);
+            fprintf(output, "node%p -> node%p [label=unop]\n", (void*)node, (void*)node->vals.unop);
+            break;
+
+        case EXPR_CONST:
+            fprintf(output, "node%p [label=\"CONST: %.2f\"]\n", (void*)node, node->vals.num);
+            break;
+
+        case EXPR_X:
+            fprintf(output, "node%p [label=\"X\"]\n", (void*)node);
+            break;
+
+        case EXPR_FN:
+        {
+            size_t i;
+            const struct math_function *fn = &fns_get()[node->vals.fn.fn_idx];
+
+            fprintf(output, "node%p [label=\"FN: %s\"]\n", (void*)node, fn->name);
+
+            for (i = 0; i < fn->num_args; ++i) {
+                struct expr_node *arg = node->vals.fn.args[i];
+                debug_print_gv(arg, output);
+                fprintf(output, "node%p -> node%p [label=arg%d]\n", (void*)node, (void*)arg, (int)i + 1);
+            }
+
+            break;
+        }
+
+        default:
+            break;
+    }
+}
+
+void node_debug_print_gv(const struct expr_node *node, FILE *output) {
+    fprintf(output, "digraph G {\n");
+    debug_print_gv(node, output);
+    fprintf(output, "}\n");
+}
+
+#endif /* ENABLE_GRAPHVIZ_EXPORT */
+
+void node_free(struct expr_node *node) {
+    if (!node) return;
+
+    switch (node->type) {
+        case EXPR_ADD:
+        case EXPR_SUB:
+        case EXPR_MULT:
+        case EXPR_DIV:
+        case EXPR_POW:
+            node_free(node->vals.binop.left);
+            node_free(node->vals.binop.right);
+            break;
+
+        case EXPR_NEG:
+            node_free(node->vals.unop);
+            break;
+
+        case EXPR_FN:
+            {
+                size_t i, num_args = fns_get()[node->vals.fn.fn_idx].num_args;
+                for (i = 0; i < num_args; ++i) {
+                    node_free(node->vals.fn.args[i]);
+                }
+            }
+            break;
+
+        default:
+            break;
+    }
+
+    free(node);
+}
diff --git a/tree.h b/tree.h
new file mode 100755
index 0000000..c896044
--- /dev/null
+++ b/tree.h
@@ -0,0 +1,151 @@
+#ifndef TREE_H
+#define TREE_H
+
+#include "lex.h"
+#include "math_functions.h"
+
+/**
+ * @brief Typ uzlu
+ */
+enum expr_type {
+    EXPR_CONST,
+    EXPR_NEG,
+    EXPR_ADD,
+    EXPR_SUB,
+    EXPR_MULT,
+    EXPR_DIV,
+    EXPR_POW,
+    EXPR_X,
+    EXPR_FN
+};
+
+/**
+ * @brief Uzel výrazu
+ */
+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;
+};
+
+/**
+ * @brief Vytvoří uzel reprezentující konstantu
+ * 
+ * @param val Hodnota konstanty
+ * @return Adresa uzlu
+ */
+struct expr_node *node_create_const(double val);
+
+/**
+ * @brief Vytvoří uzel reprezentující negaci výrazu
+ *
+ * @param unop Negovaný výraz (unární operand)
+ * @return Adresa uzlu
+ */
+struct expr_node *node_create_neg(struct expr_node *unop);
+
+/**
+ * @brief Vytvoří uzel reprezentující sčítání
+ *
+ * @param left Levý operand
+ * @param right Pravý operand
+ * @return Adresa uzlu
+ */
+struct expr_node *node_create_add(struct expr_node *left, struct expr_node *right);
+
+/**
+ * @brief Vytvoří uzel reprezentující odčítání
+ *
+ * @param left Levý operand
+ * @param right Pravý operand
+ * @return Adresa uzlu
+ */
+struct expr_node *node_create_sub(struct expr_node *left, struct expr_node *right);
+
+/**
+ * @brief Vytvoří uzel reprezentující násobení
+ *
+ * @param left Levý operand
+ * @param right Pravý operand
+ * @return Adresa uzlu
+ */
+struct expr_node *node_create_mult(struct expr_node *left, struct expr_node *right);
+
+/**
+ * @brief Vytvoří uzel reprezentující dělení
+ *
+ * @param left Levý operand
+ * @param right Pravý operand
+ * @return Adresa uzlu
+ */
+struct expr_node *node_create_div(struct expr_node *left, struct expr_node *right);
+
+/**
+ * @brief Vytvoří uzel reprezentující mocnění
+ *
+ * @param left Levý operand
+ * @param right Pravý operand
+ * @return Adresa uzlu
+ */
+struct expr_node *node_create_pow(struct expr_node *base, struct expr_node *power);
+
+/**
+ * @brief Vytvoří uzel reprezentující proměnnou
+ *
+ * @return Adresa uzlu
+ */
+struct expr_node *node_create_x(void);
+
+/**
+ * @brief Vytvoří uzel reprezentující funkci
+ *
+ * @param fn_idx Index funkce v poli vráceném fns_get()
+ * @param args Ukazatel na pole uzlů, které funkce obdrží jako argumenty
+ * @return Adresa uzlu
+ */
+struct expr_node *node_create_fn(size_t fn_idx, struct expr_node **args);
+
+/**
+ * @brief Vyhodnotí uzel
+ * 
+ * @param node Uzel
+ * @param x Proměnná
+ * @param y Výsledek vyhodnocení
+ * @return Stav vyhodnocení
+ */
+enum eval_result node_eval(const struct expr_node *node, double x, double *y);
+
+#ifdef ENABLE_GRAPHVIZ_EXPORT
+void node_debug_print_gv(const struct expr_node *node, FILE *output);
+#endif /* ENABLE_GRAPHVIZ_EXPORT */
+
+/**
+ * @brief Uvolní uzel
+ * 
+ * Tato funkce uvolní i potomky uzlu (operandy, argumenty, ...)
+ * 
+ * @param node Uzel pro uvolnění
+ */
+void node_free(struct expr_node *node);
+
+
+#endif /* TREE_H */
\ No newline at end of file