Merge pull request #74 from Servostar/69-better-parser-error-messages
69 better parser error messages
This commit is contained in:
commit
569b01c0f8
|
@ -15,3 +15,6 @@ parser.tab.c
|
||||||
parser.tab.h
|
parser.tab.h
|
||||||
build
|
build
|
||||||
/Testing/
|
/Testing/
|
||||||
|
CTestTestfile.cmake
|
||||||
|
DartConfiguration.tcl
|
||||||
|
*.cmake
|
||||||
|
|
|
@ -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}
|
add_custom_command(OUTPUT ${YACC_GENERATED_SOURCE_FILE}
|
||||||
COMMAND yacc
|
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"
|
COMMENT "generate C source file for parser"
|
||||||
VERBATIM)
|
VERBATIM)
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,21 @@
|
||||||
%{
|
%{
|
||||||
#include <yacc/parser.tab.h>
|
#include <yacc/parser.tab.h>
|
||||||
#include <sys/log.h>
|
#include <sys/log.h>
|
||||||
|
#include <lex/util.h>
|
||||||
|
|
||||||
int yyLineNumber = 1;
|
int yyLineNumber = 1;
|
||||||
|
|
||||||
int yylex();
|
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 */
|
/* 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); };
|
[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])*\" {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);};
|
\"\"\"([^\"\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 */ };
|
||||||
%%
|
%%
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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_
|
|
@ -2,11 +2,11 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/log.h>
|
#include <sys/log.h>
|
||||||
#include <yacc/parser.tab.h>
|
#include <yacc/parser.tab.h>
|
||||||
|
#include <sys/col.h>
|
||||||
|
#include <lex/util.h>
|
||||||
|
|
||||||
#define LOG_LEVEL LOG_LEVEL_DEBUG
|
#define LOG_LEVEL LOG_LEVEL_DEBUG
|
||||||
|
|
||||||
extern FILE *yyin;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Log a debug message to inform about beginning exit procedures
|
* @brief Log a debug message to inform about beginning exit procedures
|
||||||
*
|
*
|
||||||
|
@ -41,6 +41,10 @@ void setup(void) {
|
||||||
// actual setup
|
// actual setup
|
||||||
AST_init();
|
AST_init();
|
||||||
|
|
||||||
|
col_init();
|
||||||
|
|
||||||
|
lex_init();
|
||||||
|
|
||||||
DEBUG("finished starting up gemstone...");
|
DEBUG("finished starting up gemstone...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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_
|
|
@ -1,9 +1,15 @@
|
||||||
|
%locations
|
||||||
|
%define parse.error verbose
|
||||||
|
|
||||||
%{
|
%{
|
||||||
#include <sys/log.h>
|
#include <sys/log.h>
|
||||||
|
#include <sys/col.h>
|
||||||
|
|
||||||
|
int yyerror(const char*);
|
||||||
|
|
||||||
|
extern char* buffer;
|
||||||
extern int yylineno;
|
extern int yylineno;
|
||||||
|
|
||||||
int yyerror(char*);
|
|
||||||
|
|
||||||
extern int yylex();
|
extern int yylex();
|
||||||
%}
|
%}
|
||||||
|
|
||||||
|
@ -55,6 +61,7 @@
|
||||||
%token FunFunname
|
%token FunFunname
|
||||||
%token FunLineno
|
%token FunLineno
|
||||||
%token FunExtsupport
|
%token FunExtsupport
|
||||||
|
%token Invalid
|
||||||
|
|
||||||
/* Operator associativity */
|
/* Operator associativity */
|
||||||
%right '='
|
%right '='
|
||||||
|
@ -222,7 +229,70 @@ opbit: expr OpBitand expr
|
||||||
| OpBitnot expr %prec OpBitand;
|
| OpBitnot expr %prec OpBitand;
|
||||||
%%
|
%%
|
||||||
|
|
||||||
int yyerror(char *s) {
|
|
||||||
ERROR("%s", s);
|
const char* ERROR = "error";
|
||||||
return 0;
|
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);
|
||||||
}
|
}
|
Loading…
Reference in New Issue