231 lines
6.3 KiB
C
231 lines
6.3 KiB
C
#include "lex.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "math_functions.h"
|
|
|
|
#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) {
|
|
if (!*str) {
|
|
lex->p = temp_p;
|
|
return 1;
|
|
}
|
|
|
|
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;
|
|
val = strtod(lex->p, &end);
|
|
if (lex->p == end) return 0;
|
|
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) {
|
|
skip_whitespace(lex);
|
|
lex->prev_p = lex->p;
|
|
|
|
if (!*lex->p) {
|
|
lex->tok.type = TOK_EOF;
|
|
return;
|
|
}
|
|
|
|
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;
|
|
|
|
if (try_token(lex, "(", TOK_LEFT_PAREN)) return;
|
|
if (try_token(lex, ")", TOK_RIGHT_PAREN)) return;
|
|
if (try_token(lex, ",", TOK_COMMA)) return;
|
|
|
|
if (try_constant(lex, "pi", CONST_PI)) return;
|
|
if (try_constant(lex, "e", CONST_E)) return;
|
|
if (try_constant(lex, "skibidi", CONST_S)) return;
|
|
|
|
if (try_advance_identifier(lex, lex->variable_name)) {
|
|
lex->tok.type = TOK_VARIABLE;
|
|
return;
|
|
}
|
|
|
|
if (try_fns(lex)) return;
|
|
|
|
if (try_number(lex)) return;
|
|
|
|
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;
|
|
}
|
|
|
|
struct token *lex_token(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>";
|
|
}
|
|
}
|
|
|
|
#ifdef LEX_DEBUG
|
|
|
|
void lex_debug_print_token(const struct token *tok) {
|
|
printf("%-20s ", lex_token_str(tok->type));
|
|
|
|
if (tok->type == TOK_NUMBER)
|
|
printf("%.2f\n", tok->val.num);
|
|
else if (tok->type == TOK_FUNCTION) {
|
|
printf("%s\n", fns_get()[tok->val.fn_idx].name);
|
|
}
|
|
else
|
|
printf("\n");
|
|
}
|
|
|
|
#endif /* LEX_DEBUG */
|