diff --git a/.gitignore b/.gitignore index bb676ce..10613c6 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ parser.tab.c parser.tab.h build /Testing/ +CTestTestfile.cmake +DartConfiguration.tcl +*.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ffa084..213143c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,8 +61,10 @@ set(YACC_SOURCE_FILE ${PROJECT_SOURCE_DIR}/src/yacc/parser.y) set(YACC_GENERATED_SOURCE_FILE ${PROJECT_SOURCE_DIR}/src/yacc/parser.tab.c) add_custom_command(OUTPUT ${YACC_GENERATED_SOURCE_FILE} + COMMAND bison - ARGS -Wcounterexamples -d -o ${YACC_GENERATED_SOURCE_FILE} ${YACC_SOURCE_FILE} + ARGS -Wno-yacc -Wcounterexamples -d -o ${YACC_GENERATED_SOURCE_FILE} ${YACC_SOURCE_FILE} + COMMENT "generate C source file for parser" VERBATIM) diff --git a/src/lex/lexer.l b/src/lex/lexer.l index 2307af7..7c0cc0c 100644 --- a/src/lex/lexer.l +++ b/src/lex/lexer.l @@ -2,9 +2,21 @@ %{ #include #include + #include int yyLineNumber = 1; + int yylex(); + + extern int yyerror(const char* s); + + #define YY_USER_ACTION beginToken(yytext); + + #define YY_INPUT(buf,result,max_size) {\ + result = nextChar(buf); \ + if ( result <= 0 ) \ + result = YY_NULL; \ + } %} /* disable the following functions */ @@ -79,6 +91,7 @@ [0-9]+ {DEBUG("\"%s\" tokenized with \'ValInt\'", yytext); yylval.string = strdup(yytext); return(ValInt); }; [0-9]*\.[0-9]+ {DEBUG("\"%s\" tokenized with \'ValFloat\'", yytext); yylval.string = strdup(yytext); return(ValFloat);}; [a-zA-Z_0-9]+ {DEBUG("\"%s\" tokenized with \'Ident\'", yytext); yylval.string = strdup(yytext); return(Ident); }; + \"([^\"\n])*\" { yytext = yytext +1; yytext[yyleng - 2] = 0; @@ -89,5 +102,6 @@ yytext[yyleng - 4] = 0; DEBUG("\"%s\" tokenized with \'ValMultistr\'", yytext); yylval.string = strdup(yytext); return(ValMultistr);}; -.; +[ \r\t] { /* ignore whitespace */ }; +. { return yytext[0]; /* passthrough unknown token, let parser handle the error */ }; %% diff --git a/src/lex/util.c b/src/lex/util.c new file mode 100644 index 0000000..b5b92ac --- /dev/null +++ b/src/lex/util.c @@ -0,0 +1,79 @@ + +#include +#include +#include + +// implementation based on: +// https://github.com/sunxfancy/flex-bison-examples/blob/master/error-handling/ccalc.c + +char* buffer = NULL; + +static int eof = 0; +static int nRow = 0; +static int nBuffer = 0; +static int lBuffer = 0; +static int nTokenStart = 0; +static int nTokenLength = 0; +static int nTokenNextStart = 0; + +static void lex_deinit(void) { + free(buffer); +} + +void lex_init(void) { + buffer = malloc(MAX_READ_BUFFER_SIZE); + atexit(lex_deinit); +} + +void beginToken(char *t) { + nTokenStart = nTokenNextStart; + nTokenLength = (int) strlen(t); + nTokenNextStart = nBuffer + 1; + + yylloc.first_line = nRow; + yylloc.first_column = nTokenStart; + yylloc.last_line = nRow; + yylloc.last_column = nTokenStart + nTokenLength - 1; +} + +int nextChar(char *dst) { + int frc; + + if (eof) + return 0; + + while (nBuffer >= lBuffer) { + frc = getNextLine(); + if (frc != 0) { + return 0; + } + } + + dst[0] = buffer[nBuffer]; + nBuffer += 1; + + return dst[0] != 0; +} + +int getNextLine(void) { + char *p; + + nBuffer = 0; + nTokenStart = -1; + nTokenNextStart = 1; + eof = 0; + + p = fgets(buffer, MAX_READ_BUFFER_SIZE, yyin); + if (p == NULL) { + if (ferror(yyin)) { + return -1; + } + eof = 1; + return 1; + } + + nRow += 1; + lBuffer = (int) strlen(buffer); + + return 0; +} diff --git a/src/lex/util.h b/src/lex/util.h new file mode 100644 index 0000000..12e0837 --- /dev/null +++ b/src/lex/util.h @@ -0,0 +1,37 @@ + +#ifndef LEX_UTIL_H_ +#define LEX_UTIL_H_ + +#include +#include + +#define MAX_READ_BUFFER_SIZE 1000 + +extern FILE* yyin; +extern YYLTYPE yylloc; +extern char* buffer; + +/** + * @brief Initialize global state needed for the lexer + */ +void lex_init(void); + +/** + * @brief Begin counting a new token. This will fill the global struct yylloc. + * @param t the text of the token. Must be null terminated + */ +[[gnu::nonnull(1)]] +void beginToken(char *t); + +/** + * @brief Stores the next character into the supplied buffer + * @param dst the buffer to store character in + */ +int nextChar(char *dst); + +/** + * @brief Reads the next line from yyin into a global buffer + */ +int getNextLine(void); + +#endif // LEX_UTIL_H_ diff --git a/src/main.c b/src/main.c index ac54e76..27bca44 100644 --- a/src/main.c +++ b/src/main.c @@ -2,6 +2,8 @@ #include #include #include +#include +#include #define LOG_LEVEL LOG_LEVEL_DEBUG @@ -42,6 +44,10 @@ void setup(void) { // actual setup AST_init(); + col_init(); + + lex_init(); + DEBUG("finished starting up gemstone..."); } diff --git a/src/sys/col.c b/src/sys/col.c new file mode 100644 index 0000000..76260ec --- /dev/null +++ b/src/sys/col.c @@ -0,0 +1,87 @@ + +#include +#include +#include +#include + +#ifdef __unix__ +#include +#elif defined(_WIN32) || defined(WIN32) +#include +#endif + +char *RED; +char *YELLOW; +char *MAGENTA; +char *CYAN; +char *GREEN; +char *RESET; +char *BOLD; +char *FAINT; + +void col_init(void) { + if (stdout_supports_ansi_esc()) { + enable_ansi_colors(); + } else { + disable_ansi_colors(); + } +} + +void disable_ansi_colors() { + DEBUG("disabling ANSI escape codes"); + + RED = ""; + YELLOW = ""; + MAGENTA = ""; + CYAN = ""; + GREEN = ""; + RESET = ""; + BOLD = ""; + FAINT = ""; +} + +void enable_ansi_colors() { + DEBUG("enabling ANSI escape codes"); + + RED = "\x1b[31m"; + YELLOW = "\x1b[33m"; + MAGENTA = "\x1b[35m"; + CYAN = "\x1b[36m"; + GREEN = "\x1b[32m"; + RESET = "\x1b[0m"; + BOLD = "\x1b[1m"; + FAINT = "\x1b[2m"; +} + +int stdout_supports_ansi_esc() { + +#ifdef __unix__ + // check if TTY + if (isatty(STDOUT_FILENO)) { + const char *colors = getenv("COLORTERM"); + // check if colors are set and allowed + if (colors != NULL && (strcmp(colors, "truecolor") == 0 || strcmp(colors, "24bit") == 0)) { + return ANSI_ENABLED; + } + } +#elif defined(_WIN32) || defined(WIN32) + // see: + // https://stackoverflow.com/questions/63913005/how-to-test-if-console-supports-ansi-color-codes + DWORD mode; + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + + if (!GetConsoleMode(hConsole, &mode)) { + ERROR("failed to get console mode"); + return ANSI_ENABLED; + } + + if ((mode & ENABLE_VIRTUAL_TERMINAL_INPUT) | + (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)) { + return ANSI_ENABLED; + } +#else +#warning "unsupported platform, ASNI escape codes disabled by default" +#endif + + return ASNI_DISABLED; +} diff --git a/src/sys/col.h b/src/sys/col.h new file mode 100644 index 0000000..125d0d6 --- /dev/null +++ b/src/sys/col.h @@ -0,0 +1,43 @@ + +#ifndef COLORS_H_ +#define COLORS_H_ + +#define ANSI_ENABLED 1 +#define ASNI_DISABLED 0 + +// Common escape codes +// can be used to print colored text +extern char *RED; +extern char *YELLOW; +extern char *MAGENTA; +extern char *CYAN; +extern char *GREEN; +extern char *RESET; +extern char *BOLD; +extern char *FAINT; + +/** + * @brief Initialize global state + */ +void col_init(void); + +/** + * @brief Enable ANSI escape codes. This will set the correct escape codes to + * the global strings above. + */ +void enable_ansi_colors(); + +/** + * @brief Disable ANSI escape codes. This will set all the above global strings to be empty. + */ +void disable_ansi_colors(); + +/** + * @brief Check if stdout may support ANSI escape codes. + * @attention This function may report escape codes to be unavailable even if they actually are. + * @return ANSI_ENABLED if escape sequences are supported ASNI_DISABLED otherwise + */ +[[nodiscard]] +int stdout_supports_ansi_esc(); + +#endif // COLORS_H_ diff --git a/src/yacc/parser.y b/src/yacc/parser.y index 5c15452..5667d24 100644 --- a/src/yacc/parser.y +++ b/src/yacc/parser.y @@ -1,10 +1,17 @@ +%locations +%define parse.error verbose + %code requires { #include #include extern int yylineno; - int yyerror(char*); + int yyerror(const char*); + + extern char* buffer; + extern int yylineno; + extern int yylex(); extern AST_NODE_PTR root; @@ -102,6 +109,7 @@ %token FunFunname %token FunLineno %token FunExtsupport +%token Invalid /* Operator associativity */ /* Operators at lower line number have lower precedence */ @@ -488,7 +496,70 @@ opbit: expr OpBitand expr {AST_NODE_PTR and = AST_new_node(AST_BitAnd, NULL); $$ = not;}; %% -int yyerror(char *s) { - ERROR("%s", s); - return 0; + +const char* ERROR = "error"; +const char* WARNING = "warning"; +const char* NOTE = "note"; + +int print_message(const char* kind, const char* message) { + // number of characters written + int char_count = 0; + // highlight to use + char* HIGHLIGHT = CYAN; + + // convert message kind into color + if (kind == ERROR) { + HIGHLIGHT = RED; + } else if (kind == WARNING) { + HIGHLIGHT = YELLOW; + } + + // print message + char_count += printf("%sfilename:%d:%d%s:%s%s %s: %s%s\n", BOLD, yylloc.first_line, yylloc.first_column, RESET, HIGHLIGHT, BOLD, kind, RESET, message); + + // print line in which error occurred + + char_count += printf(" %4d | ", yylloc.first_line); + + for (int i = 0; i < yylloc.first_column - 1; i++) { + if (buffer[i] == '\n') { + break; + } + printf("%c", buffer[i]); + } + + char_count += printf("%s%s", BOLD, HIGHLIGHT); + + for (int i = yylloc.first_column - 1; i < yylloc.last_column; i++) { + if (buffer[i] == '\n') { + break; + } + char_count += printf("%c", buffer[i]); + } + + char_count += printf("%s", RESET); + + for (int i = yylloc.last_column; buffer[i] != '\0' && buffer[i] != '\n'; i++) { + printf("%c", buffer[i]); + } + + char_count += printf("\n | "); + + for (int i = 0; i < yylloc.first_column - 1; i++) { + char_count += printf(" "); + } + + char_count += printf("%s^", HIGHLIGHT); + + for (int i = 0; i < yylloc.last_column - yylloc.first_column; i++) { + printf("~"); + } + + char_count += printf("%s\n\n", RESET); + + return char_count; +} + +int yyerror(const char *s) { + return print_message(ERROR, s); } \ No newline at end of file