ej
This commit is contained in:
commit
c8dd221b67
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
build/
|
||||
emsdk/
|
||||
.vscode/
|
||||
28
CMakeLists.txt
Executable file
28
CMakeLists.txt
Executable file
@ -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)
|
||||
29
CMakeUserPresets.json
Normal file
29
CMakeUserPresets.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
96
canvas_graph.cpp
Executable file
96
canvas_graph.cpp
Executable file
@ -0,0 +1,96 @@
|
||||
extern "C" {
|
||||
#include "canvas_graph.h"
|
||||
}
|
||||
|
||||
#include <emscripten/val.h>
|
||||
|
||||
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<val>("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<val>("getElementById", val("canvas"));
|
||||
val ctx = canvas.call<val>("getContext", val("2d"));
|
||||
double canvas_width = canvas["width"].as<double>();
|
||||
double canvas_height = canvas["height"].as<double>();
|
||||
|
||||
// clear canvas
|
||||
ctx.call<void>("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<void>("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<void>("moveTo", val(x_canvas), val(0));
|
||||
ctx.call<void>("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<void>("moveTo", val(0), val(y_canvas));
|
||||
ctx.call<void>("lineTo", val(canvas_width), val(y_canvas));
|
||||
}
|
||||
|
||||
|
||||
ctx.call<void>("stroke");
|
||||
|
||||
// graph
|
||||
ctx.set("strokeStyle", val("#000088"));
|
||||
ctx.set("lineWidth", val(2.0));
|
||||
ctx.call<void>("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<void>(cmd, val(x_canvas), val(y_canvas));
|
||||
}
|
||||
|
||||
ctx.call<void>("stroke");
|
||||
}
|
||||
14
canvas_graph.h
Executable file
14
canvas_graph.h
Executable file
@ -0,0 +1,14 @@
|
||||
#ifndef CANVAS_GRAPH_H
|
||||
#define CANVAS_GRAPH_H
|
||||
|
||||
#include <stdio.h>
|
||||
#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 */
|
||||
50
errors.c
Executable file
50
errors.c
Executable file
@ -0,0 +1,50 @@
|
||||
#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;
|
||||
/* 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;
|
||||
}
|
||||
67
errors.h
Executable file
67
errors.h
Executable file
@ -0,0 +1,67 @@
|
||||
#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 */
|
||||
238
lex.c
Executable file
238
lex.c
Executable file
@ -0,0 +1,238 @@
|
||||
#include "lex.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#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 "<eof>";
|
||||
case TOK_ERROR: return "<error>";
|
||||
case TOK_NUMBER: return "<number>";
|
||||
case TOK_PLUS: return "'+'";
|
||||
case TOK_MINUS: return "'-'";
|
||||
case TOK_MULTIPLY: return "'*'";
|
||||
case TOK_DIVIDE: return "'/'";
|
||||
case TOK_POWER: return "'^'";
|
||||
case TOK_VARIABLE: return "<variable>";
|
||||
case TOK_FUNCTION: return "<function>";
|
||||
case TOK_LEFT_PAREN: return "'('";
|
||||
case TOK_RIGHT_PAREN: return "')'";
|
||||
case TOK_COMMA: return "','";
|
||||
|
||||
default: return "<unknown token>";
|
||||
}
|
||||
}
|
||||
115
lex.h
Executable file
115
lex.h
Executable file
@ -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 */
|
||||
58
main.cpp
Normal file
58
main.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <emscripten/val.h>
|
||||
|
||||
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<val>("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", "");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
100
math_functions.c
Executable file
100
math_functions.c
Executable file
@ -0,0 +1,100 @@
|
||||
#include "math_functions.h"
|
||||
|
||||
#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; \
|
||||
*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;
|
||||
}
|
||||
41
math_functions.h
Executable file
41
math_functions.h
Executable file
@ -0,0 +1,41 @@
|
||||
#ifndef MATH_FUNCTIONS_H
|
||||
#define MATH_FUNCTIONS_H
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#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 */
|
||||
414
parser.c
Executable file
414
parser.c
Executable file
@ -0,0 +1,414 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#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 <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(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 <type> 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);
|
||||
}
|
||||
54
parser.h
Executable file
54
parser.h
Executable file
@ -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 */
|
||||
102
shell.html
Normal file
102
shell.html
Normal file
@ -0,0 +1,102 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
|
||||
<style>
|
||||
.tt {
|
||||
font-family: "Consolas", "Courier New", monospace;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas" width="800" height="600"></canvas>
|
||||
|
||||
<input type="text" id="input" oninput="fnChanged()">
|
||||
|
||||
<pre class="tt red" id="output"></pre>
|
||||
<pre class="tt" id="temp"></pre>
|
||||
|
||||
<script>
|
||||
let drawGraph = null;
|
||||
const fnInput = document.getElementById("input");
|
||||
|
||||
let centerX = 0;
|
||||
let centerY = 0;
|
||||
let zoom = 10;
|
||||
|
||||
const canvas = document.getElementById("canvas");
|
||||
|
||||
Module = {
|
||||
onRuntimeInitialized: function() {
|
||||
drawGraph = Module.cwrap("draw_graph", "void", ["string", "number", "number", "number", "number"]);
|
||||
}
|
||||
};
|
||||
|
||||
function fnChanged() {
|
||||
redraw();
|
||||
}
|
||||
|
||||
let lastX = 0;
|
||||
let lastY = 0;
|
||||
let mouseDown = false;
|
||||
|
||||
function moveCenter(event) {
|
||||
|
||||
}
|
||||
|
||||
canvas.addEventListener("mousedown", function(event) {
|
||||
mouseDown = true;
|
||||
});
|
||||
|
||||
canvas.addEventListener("mouseup", function(event) {
|
||||
mouseDown = false;
|
||||
});
|
||||
|
||||
canvas.addEventListener("mousemove", function(event) {
|
||||
if (mouseDown) {
|
||||
const dx = (event.clientX - lastX);
|
||||
const dy = (event.clientY - lastY);
|
||||
|
||||
centerX -= dx * 2 * zoom / canvas.width;
|
||||
centerY += dy * 2 * zoom / canvas.width;
|
||||
redraw();
|
||||
}
|
||||
|
||||
lastX = event.clientX;
|
||||
lastY = event.clientY;
|
||||
});
|
||||
|
||||
canvas.addEventListener("wheel", function(event) {
|
||||
event.preventDefault();
|
||||
const delta = event.deltaY;
|
||||
zoom *= Math.pow(1.1, delta * 0.01);
|
||||
redraw();
|
||||
});
|
||||
|
||||
function redraw() {
|
||||
if (drawGraph) {
|
||||
const fn = fnInput.value;
|
||||
const xMin = -zoom + centerX;
|
||||
const xMax = zoom + centerX;
|
||||
const aspect = canvas.width / canvas.height;
|
||||
const yMin = -zoom / aspect + centerY;
|
||||
const yMax = zoom / aspect + centerY;
|
||||
|
||||
drawGraph(fn, xMin, xMax, yMin, yMax);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{{{ SCRIPT }}}
|
||||
</body>
|
||||
</html>
|
||||
281
tree.c
Executable file
281
tree.c
Executable file
@ -0,0 +1,281 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <errno.h>
|
||||
#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);
|
||||
}
|
||||
151
tree.h
Executable file
151
tree.h
Executable file
@ -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 */
|
||||
Loading…
x
Reference in New Issue
Block a user