Merge pull request #1 from sebastianrcnt/agent/cli-diagnostics-tools
feat: improve diagnostics and compiler CLI tooling
This commit is contained in:
+5
-1
@@ -4,7 +4,7 @@ CPPFLAGS ?= -Isrc
|
||||
SRC = src/arena.c src/diag.c src/lexer.c src/ast.c src/parser.c src/types.c src/check.c src/emit_c.c src/driver.c
|
||||
OBJ = $(SRC:.c=.o)
|
||||
|
||||
.PHONY: all clean test dos-build
|
||||
.PHONY: all clean test test-cli dos-build
|
||||
all: fec
|
||||
|
||||
fec: $(OBJ)
|
||||
@@ -15,6 +15,10 @@ src/%.o: src/%.c
|
||||
|
||||
test: fec
|
||||
@./tests/run-tests.sh
|
||||
@sh ./tests/run-cli-tests.sh
|
||||
|
||||
test-cli: fec
|
||||
@sh ./tests/run-cli-tests.sh
|
||||
|
||||
dos-build:
|
||||
@echo "Run build-dos.bat inside FreeDOS/Open Watcom."
|
||||
|
||||
@@ -1,9 +1,62 @@
|
||||
#include "diag.h"
|
||||
|
||||
static unsigned long digits(unsigned long n)
|
||||
{
|
||||
unsigned long count=1;
|
||||
while(n>=10UL){n/=10UL;++count;}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void excerpt(const FeDiags *d, FeLoc loc)
|
||||
{
|
||||
const char *src;
|
||||
unsigned long len;
|
||||
unsigned long i=0;
|
||||
unsigned long line=1;
|
||||
unsigned long start;
|
||||
unsigned long end;
|
||||
unsigned long gutter;
|
||||
unsigned long col;
|
||||
char c;
|
||||
if(!d || !d->source || !d->source_len || !loc.line || !loc.col) return;
|
||||
src=d->source;
|
||||
len=d->source_len;
|
||||
while(i<len && line<loc.line){if(src[i++]=='\n')++line;}
|
||||
if(line!=loc.line || i>len) return;
|
||||
start=i;
|
||||
end=start;
|
||||
while(end<len && src[end]!='\n' && src[end]!='\r')++end;
|
||||
gutter=digits(loc.line);
|
||||
fputs(" ",stderr);
|
||||
fprintf(stderr,"%lu | ",loc.line);
|
||||
if(end>start) fwrite(src+start,1,(size_t)(end-start),stderr);
|
||||
fputc('\n',stderr);
|
||||
fputs(" ",stderr);
|
||||
for(i=0;i<gutter;++i) fputc(' ',stderr);
|
||||
fputs(" | ",stderr);
|
||||
col=1;
|
||||
i=start;
|
||||
while(i<end && col<loc.col){
|
||||
c=src[i++];
|
||||
fputc(c=='\t' ? '\t' : ' ',stderr);
|
||||
++col;
|
||||
}
|
||||
fputs("^\n",stderr);
|
||||
}
|
||||
|
||||
void fe_diags_init(FeDiags *d, const char *source, unsigned long source_len)
|
||||
{
|
||||
d->errors=0;
|
||||
d->warnings=0;
|
||||
d->source=source;
|
||||
d->source_len=source_len;
|
||||
}
|
||||
|
||||
void fe_diag_error(FeDiags *d, FeLoc loc, const char *msg)
|
||||
{
|
||||
d->errors++;
|
||||
fprintf(stderr, "%s:%lu:%lu: error: %s\n", loc.file ? loc.file : "<source>", loc.line, loc.col, msg);
|
||||
excerpt(d,loc);
|
||||
}
|
||||
|
||||
void fe_diag_errorf(FeDiags *d, FeLoc loc, const char *msg, const char *arg)
|
||||
@@ -12,9 +65,16 @@ void fe_diag_errorf(FeDiags *d, FeLoc loc, const char *msg, const char *arg)
|
||||
fprintf(stderr, "%s:%lu:%lu: error: ", loc.file ? loc.file : "<source>", loc.line, loc.col);
|
||||
fprintf(stderr, msg, arg);
|
||||
fputc('\n', stderr);
|
||||
excerpt(d,loc);
|
||||
}
|
||||
|
||||
void fe_diag_note(FeLoc loc, const char *msg)
|
||||
{
|
||||
fprintf(stderr, "%s:%lu:%lu: note: %s\n", loc.file ? loc.file : "<source>", loc.line, loc.col, msg);
|
||||
}
|
||||
|
||||
void fe_diag_note_src(FeDiags *d, FeLoc loc, const char *msg)
|
||||
{
|
||||
fe_diag_note(loc,msg);
|
||||
excerpt(d,loc);
|
||||
}
|
||||
|
||||
@@ -12,10 +12,14 @@ typedef struct FeLoc {
|
||||
typedef struct FeDiags {
|
||||
unsigned long errors;
|
||||
unsigned long warnings;
|
||||
const char *source;
|
||||
unsigned long source_len;
|
||||
} FeDiags;
|
||||
|
||||
void fe_diags_init(FeDiags *d, const char *source, unsigned long source_len);
|
||||
void fe_diag_error(FeDiags *d, FeLoc loc, const char *msg);
|
||||
void fe_diag_errorf(FeDiags *d, FeLoc loc, const char *msg, const char *arg);
|
||||
void fe_diag_note(FeLoc loc, const char *msg);
|
||||
void fe_diag_note_src(FeDiags *d, FeLoc loc, const char *msg);
|
||||
|
||||
#endif
|
||||
|
||||
+88
-11
@@ -13,19 +13,96 @@ static char *read_file(const char *name, unsigned long *size)
|
||||
p=(char *)malloc((unsigned long)n+1); if(!p){fclose(f);return 0;}
|
||||
if(n && fread(p,1,(size_t)n,f)!=(size_t)n){free(p);fclose(f);return 0;} fclose(f);p[n]='\0';*size=(unsigned long)n;return p;
|
||||
}
|
||||
|
||||
static void usage(void)
|
||||
{ puts("usage: fec [--dump-ast|--emit-c] file.fe [--target=bits16|bits32] [-o output.c]"); }
|
||||
{
|
||||
puts("usage: fec [--dump-tokens|--dump-ast|--check|--emit-c] file.fe [--target=bits16|bits32] [-o output.c]");
|
||||
}
|
||||
|
||||
static void dump_tokens(const char *src, unsigned long n, const char *file,
|
||||
FeDiags *d)
|
||||
{
|
||||
FeLexer lexer;
|
||||
FeToken tok;
|
||||
fe_lexer_init(&lexer,src,n,file,d);
|
||||
do {
|
||||
tok=fe_lexer_next(&lexer);
|
||||
fprintf(stdout,"%lu:%lu\t%s\t",tok.loc.line,tok.loc.col,
|
||||
fe_token_name(tok.kind));
|
||||
if(tok.length) fwrite(tok.begin,1,(size_t)tok.length,stdout);
|
||||
else fputc('-',stdout);
|
||||
fputc('\n',stdout);
|
||||
} while(tok.kind!=FE_TOK_EOF);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int i,dump=0,emit=0,no_checks=0; const char *file=0,*outname=0; unsigned long n; char *src; FeDiags d; FeAst ast; FeParser p; FeCheck check; FeEmitter emitter; FILE *out; unsigned pointer_bits=32;
|
||||
(void)emit;
|
||||
int i,dump=0,dump_tok=0,check_only=0,emit=0,no_checks=0;
|
||||
const char *file=0,*outname=0;
|
||||
unsigned long n;
|
||||
char *src;
|
||||
FeDiags d;
|
||||
FeAst ast;
|
||||
FeParser p;
|
||||
FeCheck check;
|
||||
FeEmitter emitter;
|
||||
FILE *out;
|
||||
unsigned pointer_bits=32;
|
||||
if(argc<2){usage();return 2;}
|
||||
for(i=1;i<argc;i++) { if(strcmp(argv[i],"--dump-ast")==0) dump=1; else if(strcmp(argv[i],"--emit-c")==0) emit=1; else if(strcmp(argv[i],"-o")==0){if(i+1>=argc){fprintf(stderr,"fec: -o needs a path\n");return 2;}outname=argv[++i];} else if(strncmp(argv[i],"-o",2)==0 && argv[i][2]) outname=argv[i]+2; else if(strncmp(argv[i],"--target=bits16",15)==0) pointer_bits=16; else if(strncmp(argv[i],"--target=bits32",15)==0) pointer_bits=32; else if(strcmp(argv[i],"--no-checks")==0) no_checks=1; else if(strncmp(argv[i],"--target=",9)==0 || strncmp(argv[i],"--model=",8)==0 || strcmp(argv[i],"--strip-error-names")==0) { } else if(argv[i][0]!='-') file=argv[i]; else if(strcmp(argv[i],"--help")==0){usage();return 0;} else {fprintf(stderr,"fec: unknown option %s\n",argv[i]);return 2;} }
|
||||
if(!file){fprintf(stderr,"fec: no input file\n");return 2;}
|
||||
src=read_file(file,&n);if(!src)return 2;d.errors=0;d.warnings=0;fe_ast_init(&ast);fe_parser_init(&p,&ast,src,n,file,&d);ast.root=fe_parse_unit(&p);
|
||||
if(dump) { fe_ast_dump(ast.root,0,stdout); fe_ast_destroy(&ast); free(src); return d.errors?1:0; }
|
||||
fe_check_init(&check,&ast,&d,pointer_bits,no_checks); if(!fe_check_program(&check)){fe_ast_destroy(&ast);free(src);return 1;}
|
||||
out=outname?fopen(outname,"w"):stdout; if(!out){fprintf(stderr,"fec: cannot create %s\n",outname);fe_ast_destroy(&ast);free(src);return 2;}
|
||||
fe_emit_c_init(&emitter,out,&check,pointer_bits,no_checks);fe_emit_c_program(&emitter);if(outname)fclose(out);
|
||||
fe_ast_destroy(&ast); free(src); return d.errors?1:0;
|
||||
for(i=1;i<argc;i++) {
|
||||
if(strcmp(argv[i],"--dump-ast")==0) dump=1;
|
||||
else if(strcmp(argv[i],"--dump-tokens")==0) dump_tok=1;
|
||||
else if(strcmp(argv[i],"--check")==0) check_only=1;
|
||||
else if(strcmp(argv[i],"--emit-c")==0) emit=1;
|
||||
else if(strcmp(argv[i],"-o")==0){if(i+1>=argc){fprintf(stderr,"fec: -o needs a path\n");return 2;}outname=argv[++i];}
|
||||
else if(strncmp(argv[i],"-o",2)==0 && argv[i][2]) outname=argv[i]+2;
|
||||
else if(strncmp(argv[i],"--target=bits16",15)==0) pointer_bits=16;
|
||||
else if(strncmp(argv[i],"--target=bits32",15)==0) pointer_bits=32;
|
||||
else if(strcmp(argv[i],"--no-checks")==0) no_checks=1;
|
||||
else if(strncmp(argv[i],"--target=",9)==0 || strncmp(argv[i],"--model=",8)==0 || strcmp(argv[i],"--strip-error-names")==0) { }
|
||||
else if(argv[i][0]!='-') file=argv[i];
|
||||
else if(strcmp(argv[i],"--help")==0){usage();return 0;}
|
||||
else {fprintf(stderr,"fec: unknown option %s\n",argv[i]);return 2;}
|
||||
}
|
||||
if((dump?1:0)+(dump_tok?1:0)+(check_only?1:0)+(emit?1:0)>1){
|
||||
fprintf(stderr,"fec: choose only one output mode\n");
|
||||
return 2;
|
||||
}
|
||||
if(!file){fprintf(stderr,"fec: no input file\n");return 2;}
|
||||
src=read_file(file,&n);
|
||||
if(!src)return 2;
|
||||
fe_diags_init(&d,src,n);
|
||||
if(dump_tok){
|
||||
dump_tokens(src,n,file,&d);
|
||||
free(src);
|
||||
return d.errors?1:0;
|
||||
}
|
||||
fe_ast_init(&ast);
|
||||
fe_parser_init(&p,&ast,src,n,file,&d);
|
||||
ast.root=fe_parse_unit(&p);
|
||||
if(dump){
|
||||
fe_ast_dump(ast.root,0,stdout);
|
||||
fe_ast_destroy(&ast);
|
||||
free(src);
|
||||
return d.errors?1:0;
|
||||
}
|
||||
fe_check_init(&check,&ast,&d,pointer_bits,no_checks);
|
||||
if(!fe_check_program(&check)){
|
||||
fe_ast_destroy(&ast);
|
||||
free(src);
|
||||
return 1;
|
||||
}
|
||||
if(check_only){
|
||||
fe_ast_destroy(&ast);
|
||||
free(src);
|
||||
return 0;
|
||||
}
|
||||
out=outname?fopen(outname,"w"):stdout;
|
||||
if(!out){fprintf(stderr,"fec: cannot create %s\n",outname);fe_ast_destroy(&ast);free(src);return 2;}
|
||||
fe_emit_c_init(&emitter,out,&check,pointer_bits,no_checks);
|
||||
fe_emit_c_program(&emitter);
|
||||
if(outname)fclose(out);
|
||||
fe_ast_destroy(&ast);
|
||||
free(src);
|
||||
return d.errors?1:0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
root=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT HUP INT TERM
|
||||
|
||||
"$root"/fec --dump-tokens "$root"/tests/pass/basic.fe >"$tmp/tokens.out"
|
||||
grep 'unit' "$tmp/tokens.out" >/dev/null
|
||||
grep 'identifier' "$tmp/tokens.out" >/dev/null
|
||||
grep 'eof' "$tmp/tokens.out" >/dev/null
|
||||
|
||||
"$root"/fec --check "$root"/tests/m2/hello.fe >"$tmp/check.out"
|
||||
if [ -s "$tmp/check.out" ]; then
|
||||
echo "FAIL: --check produced stdout"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if "$root"/fec --check "$root"/tests/m2/bad-condition.fe >"$tmp/bad.out" 2>"$tmp/diag.out"; then
|
||||
echo "FAIL: --check accepted invalid input"
|
||||
exit 1
|
||||
fi
|
||||
grep 'error:' "$tmp/diag.out" >/dev/null
|
||||
grep '^ [0-9][0-9]* | ' "$tmp/diag.out" >/dev/null
|
||||
grep ' | .*\^' "$tmp/diag.out" >/dev/null
|
||||
|
||||
echo "CLI tests: token dump, check mode, and source diagnostics passed"
|
||||
Reference in New Issue
Block a user