Merge pull request #74 from Servostar/69-better-parser-error-messages

69 better parser error messages
This commit is contained in:
Filleo 2024-05-15 10:03:41 +02:00 committed by GitHub
commit 569b01c0f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 346 additions and 10 deletions

3
.gitignore vendored
View File

@ -15,3 +15,6 @@ parser.tab.c
parser.tab.h
build
/Testing/
CTestTestfile.cmake
DartConfiguration.tcl
*.cmake

View File

@ -62,7 +62,7 @@ set(YACC_GENERATED_SOURCE_FILE ${PROJECT_SOURCE_DIR}/src/yacc/parser.tab.c)
add_custom_command(OUTPUT ${YACC_GENERATED_SOURCE_FILE}
COMMAND yacc
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)

View File

@ -2,9 +2,21 @@
%{
#include <yacc/parser.tab.h>
#include <sys/log.h>
#include <lex/util.h>
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 */
@ -81,5 +93,6 @@
[a-zA-Z_0-9]+ {DEBUG("\"%s\" tokenized with \'Ident\'", yytext); yylval.string = strdup(yytext); return(Ident); };
\"([^\"\n])*\" {DEBUG("\"%s\" tokenized with \'ValStr\'", yytext); yylval.string = strdup(yytext); return(ValStr);};
\"\"\"([^\"\n]|\\\n)*\"\"\" {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 */ };
%%

79
src/lex/util.c Normal file
View File

@ -0,0 +1,79 @@
#include <lex/util.h>
#include <string.h>
#include <stdlib.h>
// 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;
}

37
src/lex/util.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef LEX_UTIL_H_
#define LEX_UTIL_H_
#include <yacc/parser.tab.h>
#include <stdio.h>
#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_

View File

@ -2,11 +2,11 @@
#include <stdlib.h>
#include <sys/log.h>
#include <yacc/parser.tab.h>
#include <sys/col.h>
#include <lex/util.h>
#define LOG_LEVEL LOG_LEVEL_DEBUG
extern FILE *yyin;
/**
* @brief Log a debug message to inform about beginning exit procedures
*
@ -41,6 +41,10 @@ void setup(void) {
// actual setup
AST_init();
col_init();
lex_init();
DEBUG("finished starting up gemstone...");
}

87
src/sys/col.c Normal file
View File

@ -0,0 +1,87 @@
#include <stdlib.h>
#include <string.h>
#include <sys/col.h>
#include <sys/log.h>
#ifdef __unix__
#include <unistd.h>
#elif defined(_WIN32) || defined(WIN32)
#include <Windows.h>
#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;
}

43
src/sys/col.h Normal file
View File

@ -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_

View File

@ -1,9 +1,15 @@
%locations
%define parse.error verbose
%{
#include <sys/log.h>
#include <sys/col.h>
int yyerror(const char*);
extern char* buffer;
extern int yylineno;
int yyerror(char*);
extern int yylex();
%}
@ -55,6 +61,7 @@
%token FunFunname
%token FunLineno
%token FunExtsupport
%token Invalid
/* Operator associativity */
%right '='
@ -222,7 +229,70 @@ opbit: expr OpBitand expr
| OpBitnot expr %prec OpBitand;
%%
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);
}