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
|
||||
build
|
||||
/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}
|
||||
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)
|
||||
|
||||
|
|
|
@ -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 */ };
|
||||
%%
|
||||
|
|
|
@ -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 <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...");
|
||||
}
|
||||
|
||||
|
|
|
@ -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/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);
|
||||
}
|
Loading…
Reference in New Issue