diff --git a/fec/Makefile b/fec/Makefile index 7f74ee8..17c4f66 100644 --- a/fec/Makefile +++ b/fec/Makefile @@ -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." diff --git a/fec/src/diag.c b/fec/src/diag.c index 21612ce..c9d35b1 100644 --- a/fec/src/diag.c +++ b/fec/src/diag.c @@ -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(ilen) return; + start=i; + end=start; + while(endstart) fwrite(src+start,1,(size_t)(end-start),stderr); + fputc('\n',stderr); + fputs(" ",stderr); + for(i=0;ierrors=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 : "", 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 : "", 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 : "", 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); +} diff --git a/fec/src/diag.h b/fec/src/diag.h index 6211e84..300f90f 100644 --- a/fec/src/diag.h +++ b/fec/src/diag.h @@ -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 diff --git a/fec/src/driver.c b/fec/src/driver.c index bfaa518..3c3fc20 100644 --- a/fec/src/driver.c +++ b/fec/src/driver.c @@ -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){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;} } + for(i=1;i=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;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; + 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; } diff --git a/fec/tests/run-cli-tests.sh b/fec/tests/run-cli-tests.sh new file mode 100644 index 0000000..ddecda3 --- /dev/null +++ b/fec/tests/run-cli-tests.sh @@ -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"