Merge pull request #1 from sebastianrcnt/agent/cli-diagnostics-tools

feat: improve diagnostics and compiler CLI tooling
This commit is contained in:
정시원
2026-08-16 14:22:16 +09:00
committed by GitHub
5 changed files with 182 additions and 11 deletions
+5 -1
View File
@@ -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."
+60
View File
@@ -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);
}
+4
View File
@@ -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
View File
@@ -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;
}
+26
View File
@@ -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"