diff --git a/.gitignore b/.gitignore index 4920e87..c9ce8fb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ !.qemu/*.txt !.qemu/share/ !.qemu/share/*.c +.qemu/share/fec/ .qemu/*.qcow2 .qemu/*.img diff --git a/fec/Makefile b/fec/Makefile new file mode 100644 index 0000000..0891f8f --- /dev/null +++ b/fec/Makefile @@ -0,0 +1,23 @@ +CC ?= cc +CFLAGS ?= -O2 -Wall -Wextra -std=c89 +CPPFLAGS ?= -Isrc +SRC = src/arena.c src/diag.c src/lexer.c src/ast.c src/parser.c src/driver.c +OBJ = $(SRC:.c=.o) + +.PHONY: all clean test dos-build +all: fec + +fec: $(OBJ) + $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ $(OBJ) + +src/%.o: src/%.c + $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< + +test: fec + @./tests/run-tests.sh + +dos-build: + @echo "Run build-dos.bat inside FreeDOS/Open Watcom." + +clean: + $(RM) $(OBJ) fec diff --git a/fec/build-dos.bat b/fec/build-dos.bat new file mode 100644 index 0000000..510bbfa --- /dev/null +++ b/fec/build-dos.bat @@ -0,0 +1,29 @@ +@echo off +rem Open Watcom C89 build. TEST-DOS.BAT runs this from C:\FEC. +C: +cd \FEC +if exist BUILD.OK del BUILD.OK +if exist BUILD.FAIL del BUILD.FAIL +if exist fec.exe del fec.exe +if exist __wcl__.lnk del __wcl__.lnk +if exist arena.obj del arena.obj +if exist diag.obj del diag.obj +if exist lexer.obj del lexer.obj +if exist ast.obj del ast.obj +if exist parser.obj del parser.obj +if exist driver.obj del driver.obj + +if "%WATCOM%"=="" set WATCOM=C:\DEVEL\WATCOMC +if not exist %WATCOM%\BINW\WCL.EXE goto build_fail +set PATH=%WATCOM%\BINW;%WATCOM%\BINP;%PATH% +wcl -q -za -wx -bt=dos -k32768 -fe=fec.exe src\arena.c src\diag.c src\lexer.c src\ast.c src\parser.c src\driver.c +if errorlevel 1 goto build_fail +if not exist fec.exe goto build_fail +echo OK>BUILD.OK +cd C:\FEC +goto build_done + +:build_fail +echo FAIL>BUILD.FAIL + +:build_done diff --git a/fec/src/arena.c b/fec/src/arena.c new file mode 100644 index 0000000..b7a0bf2 --- /dev/null +++ b/fec/src/arena.c @@ -0,0 +1,56 @@ +#include "arena.h" +#include +#include + +struct FeArenaBlock { + FeArenaBlock *next; + size_t used; + size_t size; + unsigned char data[1]; +}; + +void fe_arena_init(FeArena *a, size_t block_size) +{ + a->blocks = 0; + a->block_size = block_size ? block_size : 16384; +} + +void fe_arena_destroy(FeArena *a) +{ + FeArenaBlock *b = a->blocks; + while (b) { + FeArenaBlock *n = b->next; + free(b); + b = n; + } + a->blocks = 0; +} + +void *fe_arena_alloc(FeArena *a, size_t size) +{ + FeArenaBlock *b; + size_t need; + if (size == 0) size = 1; + need = (size + 7u) & ~(size_t)7u; + b = a->blocks; + if (!b || b->used + need > b->size) { + size_t bs = a->block_size > need ? a->block_size : need; + b = (FeArenaBlock *)malloc(sizeof(FeArenaBlock) + bs - 1); + if (!b) return 0; + b->next = a->blocks; + b->used = 0; + b->size = bs; + a->blocks = b; + } + b->used += need; + return b->data + b->used - need; +} + +char *fe_arena_strdup(FeArena *a, const char *s, size_t n) +{ + char *p = (char *)fe_arena_alloc(a, n + 1); + if (!p) return 0; + if (n) memcpy(p, s, n); + p[n] = '\0'; + return p; +} diff --git a/fec/src/arena.h b/fec/src/arena.h new file mode 100644 index 0000000..8e175f4 --- /dev/null +++ b/fec/src/arena.h @@ -0,0 +1,17 @@ +#ifndef FE_ARENA_H +#define FE_ARENA_H + +#include + +typedef struct FeArenaBlock FeArenaBlock; +typedef struct FeArena { + FeArenaBlock *blocks; + size_t block_size; +} FeArena; + +void fe_arena_init(FeArena *a, size_t block_size); +void fe_arena_destroy(FeArena *a); +void *fe_arena_alloc(FeArena *a, size_t size); +char *fe_arena_strdup(FeArena *a, const char *s, size_t n); + +#endif diff --git a/fec/src/ast.c b/fec/src/ast.c new file mode 100644 index 0000000..81b0e14 --- /dev/null +++ b/fec/src/ast.c @@ -0,0 +1,39 @@ +#include "ast.h" +#include + +void fe_ast_init(FeAst *a) { fe_arena_init(&a->arena, 32768); a->root=0; } +void fe_ast_destroy(FeAst *a) { fe_arena_destroy(&a->arena); a->root=0; } +FeNode *fe_node(FeAst *a, FeNodeKind k, FeLoc loc, const char *text, unsigned long len) +{ + FeNode *n=(FeNode *)fe_arena_alloc(&a->arena,sizeof(FeNode)); + if (!n) return 0; + n->kind=k; n->loc=loc; n->text=text?fe_arena_strdup(&a->arena,text,len):0; + n->a=n->b=n->c=n->children=n->next=0; return n; +} +void fe_node_add(FeNode *parent, FeNode *child) +{ + FeNode *p; + if (!child) return; + if (!parent->children) { parent->children=child; return; } + p=parent->children; while(p->next) p=p->next; p->next=child; +} +static void spaces(int n, FILE *out) { while(n-->0) fputc(' ',out); } +void fe_ast_dump(const FeNode *n, int indent, FILE *out) +{ + const FeNode *c; + if (!n) return; + spaces(indent,out); fprintf(out,"(%s",fe_node_name(n->kind)); + if (n->text) fprintf(out," %s",n->text); + fputc('\n',out); + if (n->a) fe_ast_dump(n->a,indent+2,out); + if (n->b) fe_ast_dump(n->b,indent+2,out); + if (n->c) fe_ast_dump(n->c,indent+2,out); + for(c=n->children;c;c=c->next) fe_ast_dump(c,indent+2,out); + spaces(indent,out); fputc(')',out); fputc('\n',out); +} +const char *fe_node_name(FeNodeKind k) +{ + static const char *names[] = {"unit","import","fn","struct","enum","error","const","global","field","param","variant","block","let","var","expr-stmt","assign","if","while","for","match","arm","return","break","continue","defer","unsafe","asm","type","expr","binary","unary","call","index","member","literal","ident","struct-init","error"}; + if ((unsigned)k >= sizeof(names)/sizeof(names[0])) return "node"; + return names[k]; +} diff --git a/fec/src/ast.h b/fec/src/ast.h new file mode 100644 index 0000000..3b26b02 --- /dev/null +++ b/fec/src/ast.h @@ -0,0 +1,41 @@ +#ifndef FE_AST_H +#define FE_AST_H + +#include "arena.h" +#include "lexer.h" +#include + +typedef enum FeNodeKind { + FE_N_UNIT, FE_N_IMPORT, FE_N_FN, FE_N_STRUCT, FE_N_ENUM, FE_N_ERROR_DECL, FE_N_CONST, + FE_N_GLOBAL, FE_N_FIELD, FE_N_PARAM, FE_N_VARIANT, FE_N_BLOCK, FE_N_LET, FE_N_VAR, + FE_N_EXPR_STMT, FE_N_ASSIGN, FE_N_IF, FE_N_WHILE, FE_N_FOR, FE_N_MATCH, FE_N_ARM, + FE_N_RETURN, FE_N_BREAK, FE_N_CONTINUE, FE_N_DEFER, FE_N_UNSAFE, FE_N_ASM, + FE_N_TYPE, FE_N_EXPR, FE_N_BINARY, FE_N_UNARY, FE_N_CALL, FE_N_INDEX, FE_N_MEMBER, + FE_N_LITERAL, FE_N_IDENT, FE_N_STRUCT_INIT, FE_N_ERROR_NODE +} FeNodeKind; + +typedef struct FeNode FeNode; +struct FeNode { + FeNodeKind kind; + FeLoc loc; + char *text; + FeNode *a; + FeNode *b; + FeNode *c; + FeNode *children; + FeNode *next; +}; + +typedef struct FeAst { + FeArena arena; + FeNode *root; +} FeAst; + +void fe_ast_init(FeAst *a); +void fe_ast_destroy(FeAst *a); +FeNode *fe_node(FeAst *a, FeNodeKind k, FeLoc loc, const char *text, unsigned long len); +void fe_node_add(FeNode *parent, FeNode *child); +void fe_ast_dump(const FeNode *n, int indent, FILE *out); +const char *fe_node_name(FeNodeKind k); + +#endif diff --git a/fec/src/diag.c b/fec/src/diag.c new file mode 100644 index 0000000..21612ce --- /dev/null +++ b/fec/src/diag.c @@ -0,0 +1,20 @@ +#include "diag.h" + +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); +} + +void fe_diag_errorf(FeDiags *d, FeLoc loc, const char *msg, const char *arg) +{ + d->errors++; + fprintf(stderr, "%s:%lu:%lu: error: ", loc.file ? loc.file : "", loc.line, loc.col); + fprintf(stderr, msg, arg); + fputc('\n', stderr); +} + +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); +} diff --git a/fec/src/diag.h b/fec/src/diag.h new file mode 100644 index 0000000..6211e84 --- /dev/null +++ b/fec/src/diag.h @@ -0,0 +1,21 @@ +#ifndef FE_DIAG_H +#define FE_DIAG_H + +#include + +typedef struct FeLoc { + const char *file; + unsigned long line; + unsigned long col; +} FeLoc; + +typedef struct FeDiags { + unsigned long errors; + unsigned long warnings; +} FeDiags; + +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); + +#endif diff --git a/fec/src/driver.c b/fec/src/driver.c new file mode 100644 index 0000000..5c52c99 --- /dev/null +++ b/fec/src/driver.c @@ -0,0 +1,25 @@ +#include "parser.h" +#include +#include +#include + +static char *read_file(const char *name, unsigned long *size) +{ + FILE *f; long n; char *p; + f=fopen(name,"rb"); if(!f){fprintf(stderr,"fec: cannot open %s\n",name);return 0;} + if(fseek(f,0L,SEEK_END)!=0){fclose(f);return 0;} n=ftell(f); if(n<0){fclose(f);return 0;} rewind(f); + 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] file.fe [--target=bits16|bits32] [--model=small|large]"); } +int main(int argc, char **argv) +{ + int i,dump=0; const char *file=0; unsigned long n; char *src; FeDiags d; FeAst ast; FeParser p; + if(argc<2){usage();return 2;} + for(i=1;i +#include + +typedef struct FeKw { const char *s; FeTokKind k; } FeKw; +static const FeKw keywords[] = { + {"unit",FE_TOK_UNIT},{"import",FE_TOK_IMPORT},{"pub",FE_TOK_PUB},{"fn",FE_TOK_FN}, + {"struct",FE_TOK_STRUCT},{"enum",FE_TOK_ENUM},{"error",FE_TOK_ERROR_KW},{"const",FE_TOK_CONST}, + {"static",FE_TOK_STATIC},{"var",FE_TOK_VAR},{"let",FE_TOK_LET},{"mut",FE_TOK_MUT}, + {"if",FE_TOK_IF},{"else",FE_TOK_ELSE},{"while",FE_TOK_WHILE},{"for",FE_TOK_FOR},{"in",FE_TOK_IN}, + {"match",FE_TOK_MATCH},{"return",FE_TOK_RETURN},{"break",FE_TOK_BREAK},{"continue",FE_TOK_CONTINUE}, + {"defer",FE_TOK_DEFER},{"unsafe",FE_TOK_UNSAFE},{"comptime",FE_TOK_COMPTIME},{"asm",FE_TOK_ASM}, + {"try",FE_TOK_TRY},{"catch",FE_TOK_CATCH},{"as",FE_TOK_AS},{"extern",FE_TOK_EXTERN}, + {"interrupt",FE_TOK_INTERRUPT},{"interrupt_safe",FE_TOK_INTERRUPT_SAFE},{"far",FE_TOK_FAR}, + {"true",FE_TOK_TRUE},{"false",FE_TOK_FALSE},{"null",FE_TOK_NULL},{"undefined",FE_TOK_UNDEFINED}, + {"shared",FE_TOK_SHARED},{"atomic",FE_TOK_ATOMIC},{"critical",FE_TOK_CRITICAL}, + {"self",FE_TOK_SELF},{"Self",FE_TOK_SELFTYPE},{"type",FE_TOK_TYPE}, + {"packed",FE_TOK_PACKED},{"orelse",FE_TOK_ORELSE},{"and",FE_TOK_AND_KW},{"or",FE_TOK_OR_KW},{"not",FE_TOK_NOT}, + {0,FE_TOK_UNKNOWN} +}; + +static int at(FeLexer *l, unsigned long n, char c) { return l->pos + n < l->length && l->src[l->pos+n] == c; } +static FeLoc here(FeLexer *l, unsigned long line, unsigned long col) +{ FeLoc x; x.file=l->file; x.line=line; x.col=col; return x; } +static char cur(FeLexer *l) { return l->pos < l->length ? l->src[l->pos] : '\0'; } +static void advance(FeLexer *l) +{ + if (l->pos >= l->length) return; + if (l->src[l->pos] == '\n') { l->line++; l->col = 1; } + else l->col++; + l->pos++; +} +static void skip_space(FeLexer *l) +{ + for (;;) { + while (isspace((unsigned char)cur(l))) advance(l); + if (at(l,0,'/') && at(l,1,'/')) { + while (cur(l) && cur(l) != '\n') advance(l); + continue; + } + if (at(l,0,'/') && at(l,1,'*')) { + unsigned long depth = 0; + advance(l); advance(l); depth = 1; + while (depth && cur(l)) { + if (at(l,0,'/') && at(l,1,'*')) { advance(l); advance(l); depth++; } + else if (at(l,0,'*') && at(l,1,'/')) { advance(l); advance(l); depth--; } + else advance(l); + } + if (depth) fe_diag_error(l->diags, here(l,l->line,l->col), "unterminated block comment"); + continue; + } + break; + } +} + +void fe_lexer_init(FeLexer *l, const char *src, unsigned long length, const char *file, FeDiags *d) +{ + l->src=src; l->length=length; l->pos=0; l->line=1; l->col=1; l->file=file; l->diags=d; +} + +static FeTokKind keyword(const char *s, unsigned long n) +{ + unsigned long i; + for (i=0; keywords[i].s; i++) { + if (strlen(keywords[i].s)==n && memcmp(keywords[i].s,s,n)==0) return keywords[i].k; + } + return FE_TOK_IDENT; +} +static FeToken tok(FeLexer *l, FeTokKind k, unsigned long start, unsigned long line, unsigned long col) +{ + FeToken t; t.kind=k; t.begin=l->src+start; t.length=l->pos-start; t.loc.file=l->file; t.loc.line=line; t.loc.col=col; return t; +} +static int digit_for_base(char c, int base) +{ + int d; + if (c >= '0' && c <= '9') d=c-'0'; + else if (c >= 'a' && c <= 'f') d=c-'a'+10; + else if (c >= 'A' && c <= 'F') d=c-'A'+10; + else return 0; + return d < base; +} + +FeToken fe_lexer_next(FeLexer *l) +{ + unsigned long start, line, col; + char c; + skip_space(l); + start=l->pos; line=l->line; col=l->col; c=cur(l); + if (!c) return tok(l,FE_TOK_EOF,start,line,col); + if (isalpha((unsigned char)c) || c=='_') { + advance(l); + while (isalnum((unsigned char)cur(l)) || cur(l)=='_') advance(l); + return tok(l,keyword(l->src+start,l->pos-start),start,line,col); + } + if (isdigit((unsigned char)c)) { + int base=10, had_digit=0; + if (c=='0' && (at(l,1,'x') || at(l,1,'X'))) { advance(l); advance(l); base=16; } + else if (c=='0' && (at(l,1,'b') || at(l,1,'B'))) { advance(l); advance(l); base=2; } + else if (c=='0' && (at(l,1,'o') || at(l,1,'O'))) { advance(l); advance(l); base=8; } + while (cur(l)=='_' || digit_for_base(cur(l),base)) { if(cur(l)!='_') had_digit=1; advance(l); } + if (!had_digit) fe_diag_error(l->diags,here(l,line,col),"integer literal has no digits"); + if (isalnum((unsigned char)cur(l))) { + fe_diag_error(l->diags,here(l,line,col),"invalid digit in integer literal"); + while (isalnum((unsigned char)cur(l)) || cur(l)=='_') advance(l); + } + return tok(l,FE_TOK_INT,start,line,col); + } + if (c=='\'' || c=='"') { + char quote=c; int bad=0, units=0; advance(l); + while (cur(l) && cur(l)!=quote) { + if (cur(l)=='\n' || cur(l)=='\r') { bad=1; break; } + units++; + if (cur(l)=='\\') { + advance(l); + if (!cur(l)) { bad=1; break; } + if (cur(l)=='x') { int i; advance(l); for(i=0;i<2;i++) { if(!digit_for_base(cur(l),16)) bad=1; else advance(l); } } + else if (cur(l)=='u') { int i; advance(l); for(i=0;i<4;i++) { if(!digit_for_base(cur(l),16)) bad=1; else advance(l); } } + else if (strchr("nrt\\'\"0",cur(l))) advance(l); + else { bad=1; advance(l); } + } else advance(l); + } + if (cur(l)==quote) advance(l); else bad=1; + if (quote=='\'' && units != 1) bad=1; + if (bad) fe_diag_error(l->diags,here(l,line,col),quote=='\''?"invalid character literal":"unterminated or invalid string literal"); + return tok(l,quote=='\''?FE_TOK_CHAR:FE_TOK_STRING,start,line,col); + } + advance(l); + switch(c) { + case '(': return tok(l,FE_TOK_LPAREN,start,line,col); case ')': return tok(l,FE_TOK_RPAREN,start,line,col); + case '{': return tok(l,FE_TOK_LBRACE,start,line,col); case '}': return tok(l,FE_TOK_RBRACE,start,line,col); + case '[': return tok(l,FE_TOK_LBRACKET,start,line,col); case ']': return tok(l,FE_TOK_RBRACKET,start,line,col); + case ',': return tok(l,FE_TOK_COMMA,start,line,col); case ';': return tok(l,FE_TOK_SEMI,start,line,col); + case ':': return tok(l,FE_TOK_COLON,start,line,col); case '@': return tok(l,FE_TOK_AT,start,line,col); + case '?': return tok(l,FE_TOK_QUESTION,start,line,col); + case '.': if (cur(l)=='.') { advance(l); return tok(l,FE_TOK_DOTDOT,start,line,col); } return tok(l,FE_TOK_DOT,start,line,col); + case '+': if(cur(l)=='='){advance(l);return tok(l,FE_TOK_PLUS_EQ,start,line,col);} if(cur(l)=='%'){advance(l);return tok(l,FE_TOK_PLUS_WRAP,start,line,col);} return tok(l,FE_TOK_PLUS,start,line,col); + case '-': if(cur(l)=='>'){advance(l);return tok(l,FE_TOK_ARROW,start,line,col);} if(cur(l)=='='){advance(l);return tok(l,FE_TOK_MINUS_EQ,start,line,col);} if(cur(l)=='%'){advance(l);return tok(l,FE_TOK_MINUS_WRAP,start,line,col);} return tok(l,FE_TOK_MINUS,start,line,col); + case '*': if(cur(l)=='='){advance(l);return tok(l,FE_TOK_STAR_EQ,start,line,col);} if(cur(l)=='%'){advance(l);return tok(l,FE_TOK_STAR_WRAP,start,line,col);} return tok(l,FE_TOK_STAR,start,line,col); + case '/': if(cur(l)=='='){advance(l);return tok(l,FE_TOK_SLASH_EQ,start,line,col);} return tok(l,FE_TOK_SLASH,start,line,col); + case '%': if(cur(l)=='='){advance(l);return tok(l,FE_TOK_PERCENT_EQ,start,line,col);} return tok(l,FE_TOK_PERCENT,start,line,col); + case '=': if(cur(l)=='='){advance(l);return tok(l,FE_TOK_EQEQ,start,line,col);} if(cur(l)=='>'){advance(l);return tok(l,FE_TOK_FATARROW,start,line,col);} return tok(l,FE_TOK_EQ,start,line,col); + case '!': if(cur(l)=='='){advance(l);return tok(l,FE_TOK_NE,start,line,col);} return tok(l,FE_TOK_BANG,start,line,col); + case '<': if(cur(l)=='='){advance(l);return tok(l,FE_TOK_LE,start,line,col);} if(cur(l)=='<'){advance(l);if(cur(l)=='='){advance(l);return tok(l,FE_TOK_SHL_EQ,start,line,col);}return tok(l,FE_TOK_SHL,start,line,col);} return tok(l,FE_TOK_LT,start,line,col); + case '>': if(cur(l)=='='){advance(l);return tok(l,FE_TOK_GE,start,line,col);} if(cur(l)=='>'){advance(l);if(cur(l)=='='){advance(l);return tok(l,FE_TOK_SHR_EQ,start,line,col);}return tok(l,FE_TOK_SHR,start,line,col);} return tok(l,FE_TOK_GT,start,line,col); + case '&': if(cur(l)=='&'){advance(l);fe_diag_error(l->diags,here(l,line,col),"&& is not a Ferro logical operator; use 'and'");return tok(l,FE_TOK_UNKNOWN,start,line,col);} if(cur(l)=='='){advance(l);return tok(l,FE_TOK_AND_EQ,start,line,col);} return tok(l,FE_TOK_AND,start,line,col); + case '|': if(cur(l)=='|'){advance(l);fe_diag_error(l->diags,here(l,line,col),"|| is not a Ferro logical operator; use 'or'");return tok(l,FE_TOK_UNKNOWN,start,line,col);} if(cur(l)=='='){advance(l);return tok(l,FE_TOK_OR_EQ,start,line,col);} return tok(l,FE_TOK_OR,start,line,col); + case '^': if(cur(l)=='='){advance(l);return tok(l,FE_TOK_XOR_EQ,start,line,col);} return tok(l,FE_TOK_XOR,start,line,col); + default: fe_diag_error(l->diags,here(l,line,col),"unknown character"); return tok(l,FE_TOK_UNKNOWN,start,line,col); + } +} + +const char *fe_token_name(FeTokKind k) +{ + switch(k) { + case FE_TOK_EOF:return "eof"; case FE_TOK_IDENT:return "identifier"; case FE_TOK_INT:return "integer"; + case FE_TOK_CHAR:return "character"; case FE_TOK_STRING:return "string"; case FE_TOK_UNIT:return "unit"; + case FE_TOK_FN:return "fn"; case FE_TOK_STRUCT:return "struct"; case FE_TOK_ENUM:return "enum"; + case FE_TOK_ERROR_KW:return "error"; case FE_TOK_CONST:return "const"; case FE_TOK_LET:return "let"; + case FE_TOK_VAR:return "var"; case FE_TOK_IF:return "if"; case FE_TOK_ELSE:return "else"; + case FE_TOK_WHILE:return "while"; case FE_TOK_FOR:return "for"; case FE_TOK_MATCH:return "match"; + case FE_TOK_RETURN:return "return"; case FE_TOK_BREAK:return "break"; case FE_TOK_CONTINUE:return "continue"; + case FE_TOK_TRUE:return "true"; case FE_TOK_FALSE:return "false"; case FE_TOK_NULL:return "null"; + case FE_TOK_UNDEFINED:return "undefined"; case FE_TOK_AND_KW:return "and"; case FE_TOK_OR_KW:return "or"; + case FE_TOK_NOT:return "not"; case FE_TOK_BANG:return "!"; + case FE_TOK_LBRACE:return "{"; case FE_TOK_RBRACE:return "}"; case FE_TOK_LPAREN:return "("; case FE_TOK_RPAREN:return ")"; + case FE_TOK_SEMI:return ";"; case FE_TOK_COLON:return ":"; case FE_TOK_COMMA:return ","; + case FE_TOK_EQ:return "="; case FE_TOK_ARROW:return "->"; case FE_TOK_FATARROW:return "=>"; + default:return "token"; + } +} diff --git a/fec/src/lexer.h b/fec/src/lexer.h new file mode 100644 index 0000000..7b1cda7 --- /dev/null +++ b/fec/src/lexer.h @@ -0,0 +1,50 @@ +#ifndef FE_LEXER_H +#define FE_LEXER_H + +#include "diag.h" +#include "arena.h" + +typedef enum FeTokKind { + FE_TOK_EOF, FE_TOK_ERROR, FE_TOK_IDENT, FE_TOK_INT, FE_TOK_CHAR, FE_TOK_STRING, + FE_TOK_UNIT, FE_TOK_IMPORT, FE_TOK_PUB, FE_TOK_FN, FE_TOK_STRUCT, FE_TOK_ENUM, + FE_TOK_ERROR_KW, FE_TOK_CONST, FE_TOK_STATIC, FE_TOK_VAR, FE_TOK_LET, FE_TOK_MUT, + FE_TOK_IF, FE_TOK_ELSE, FE_TOK_WHILE, FE_TOK_FOR, FE_TOK_IN, FE_TOK_MATCH, + FE_TOK_RETURN, FE_TOK_BREAK, FE_TOK_CONTINUE, FE_TOK_DEFER, FE_TOK_UNSAFE, + FE_TOK_COMPTIME, FE_TOK_ASM, FE_TOK_TRY, FE_TOK_CATCH, FE_TOK_AS, FE_TOK_EXTERN, + FE_TOK_INTERRUPT, FE_TOK_INTERRUPT_SAFE, FE_TOK_FAR, FE_TOK_TRUE, FE_TOK_FALSE, FE_TOK_NULL, + FE_TOK_UNDEFINED, FE_TOK_SHARED, FE_TOK_ATOMIC, FE_TOK_CRITICAL, FE_TOK_SELF, + FE_TOK_SELFTYPE, FE_TOK_TYPE, FE_TOK_PACKED, FE_TOK_ORELSE, + FE_TOK_LPAREN, FE_TOK_RPAREN, FE_TOK_LBRACE, FE_TOK_RBRACE, FE_TOK_LBRACKET, FE_TOK_RBRACKET, + FE_TOK_COMMA, FE_TOK_SEMI, FE_TOK_COLON, FE_TOK_DOT, FE_TOK_DOTDOT, + FE_TOK_PLUS, FE_TOK_MINUS, FE_TOK_STAR, FE_TOK_SLASH, FE_TOK_PERCENT, + FE_TOK_PLUS_EQ, FE_TOK_MINUS_EQ, FE_TOK_STAR_EQ, FE_TOK_SLASH_EQ, FE_TOK_PERCENT_EQ, + FE_TOK_PLUS_WRAP, FE_TOK_MINUS_WRAP, FE_TOK_STAR_WRAP, + FE_TOK_EQ, FE_TOK_EQEQ, FE_TOK_NE, FE_TOK_LT, FE_TOK_LE, FE_TOK_GT, FE_TOK_GE, + FE_TOK_AND, FE_TOK_OR, FE_TOK_AND_KW, FE_TOK_OR_KW, FE_TOK_XOR, FE_TOK_NOT, FE_TOK_BANG, FE_TOK_SHL, FE_TOK_SHR, + FE_TOK_AND_EQ, FE_TOK_OR_EQ, FE_TOK_XOR_EQ, FE_TOK_SHL_EQ, FE_TOK_SHR_EQ, + FE_TOK_ANDAND, FE_TOK_OROR, FE_TOK_ARROW, FE_TOK_FATARROW, FE_TOK_AT, + FE_TOK_QUESTION, FE_TOK_UNKNOWN +} FeTokKind; + +typedef struct FeToken { + FeTokKind kind; + const char *begin; + unsigned long length; + FeLoc loc; +} FeToken; + +typedef struct FeLexer { + const char *src; + unsigned long length; + unsigned long pos; + unsigned long line; + unsigned long col; + const char *file; + FeDiags *diags; +} FeLexer; + +void fe_lexer_init(FeLexer *l, const char *src, unsigned long length, const char *file, FeDiags *d); +FeToken fe_lexer_next(FeLexer *l); +const char *fe_token_name(FeTokKind k); + +#endif diff --git a/fec/src/parser.c b/fec/src/parser.c new file mode 100644 index 0000000..4efa04d --- /dev/null +++ b/fec/src/parser.c @@ -0,0 +1,201 @@ +#include "parser.h" +#include +#include + +static FeToken next(FeParser *p) { p->previous=p->current; p->current=fe_lexer_next(&p->lexer); return p->current; } +static int is(FeParser *p, FeTokKind k) { return p->current.kind==k; } +static int eat(FeParser *p, FeTokKind k) { if(is(p,k)){next(p);return 1;}return 0; } +static FeNode *toknode(FeParser *p, FeNodeKind k, FeToken t) { return fe_node(p->ast,k,t.loc,t.begin,t.length); } +static void error(FeParser *p, const char *s) { fe_diag_error(p->diags,p->current.loc,s); } +static int want(FeParser *p, FeTokKind k, const char *what) +{ if(eat(p,k)) return 1; error(p,what); return 0; } +static int is_name(FeParser *p) { return is(p,FE_TOK_IDENT)||is(p,FE_TOK_SELF)||is(p,FE_TOK_SELFTYPE); } +static FeNode *expr(FeParser *p, int minprec); +static FeNode *type(FeParser *p); +static FeNode *statement(FeParser *p); +static FeNode *block(FeParser *p); + +void fe_parser_init(FeParser *p, FeAst *ast, const char *src, unsigned long length, const char *file, FeDiags *d) +{ + p->ast=ast; p->diags=d; fe_lexer_init(&p->lexer,src,length,file,d); + p->previous=p->current=fe_lexer_next(&p->lexer); +} + +static void recover(FeParser *p) +{ + while(!is(p,FE_TOK_EOF) && !is(p,FE_TOK_SEMI) && !is(p,FE_TOK_RBRACE)) next(p); + if(is(p,FE_TOK_SEMI)) next(p); +} + +static FeNode *type_prefix(FeParser *p, FeTokKind k, FeToken op) +{ + FeNode *n; + (void)k; + n=toknode(p,FE_N_TYPE,op); n->a=type(p); return n; +} +static FeNode *type(FeParser *p) +{ + FeToken t=p->current; FeNode *n; + if (is(p,FE_TOK_QUESTION)||is(p,FE_TOK_BANG)||is(p,FE_TOK_STAR)||is(p,FE_TOK_XOR)) { + next(p); return type_prefix(p,t.kind,t); + } + if (is(p,FE_TOK_AND)) { + next(p); n=toknode(p,FE_N_TYPE,t); if(eat(p,FE_TOK_MUT)) n->text=fe_arena_strdup(&p->ast->arena,"&mut",4); n->a=type(p); return n; + } + if (is(p,FE_TOK_FAR)) { + next(p); n=toknode(p,FE_N_TYPE,t); if(is(p,FE_TOK_STAR)||is(p,FE_TOK_XOR)||is(p,FE_TOK_AND)) next(p); n->a=type(p); return n; + } + if (is(p,FE_TOK_LBRACKET)) { + next(p); n=toknode(p,FE_N_TYPE,t); + if(!eat(p,FE_TOK_RBRACKET)) { n->a=expr(p,0); want(p,FE_TOK_RBRACKET,"expected ']' in array type"); } + n->b=type(p); return n; + } + if (is(p,FE_TOK_FN)) { + next(p); n=toknode(p,FE_N_TYPE,t); want(p,FE_TOK_LPAREN,"expected '(' in function type"); + while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)) { fe_node_add(n,type(p)); if(!eat(p,FE_TOK_COMMA)) break; } + want(p,FE_TOK_RPAREN,"expected ')' in function type"); if(eat(p,FE_TOK_ARROW)) n->a=type(p); return n; + } + if (is_name(p) || is(p,FE_TOK_TYPE)) { + next(p); n=toknode(p,FE_N_TYPE,t); + if (eat(p,FE_TOK_DOT)) { if(is_name(p)){FeNode *m=toknode(p,FE_N_IDENT,p->previous); n->a=m; next(p);} else error(p,"expected type name after '.'"); } + if (eat(p,FE_TOK_BANG)) { FeNode *e=toknode(p,FE_N_TYPE,p->previous); e->a=n; e->b=type(p); return e; } + if (eat(p,FE_TOK_LPAREN)) { while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)){fe_node_add(n,type(p));if(!eat(p,FE_TOK_COMMA))break;} want(p,FE_TOK_RPAREN,"expected ')' in generic type"); } + return n; + } + error(p,"expected type"); next(p); return fe_node(p->ast,FE_N_TYPE,t.loc,"error",5); +} + +static int precedence(FeTokKind k) +{ + switch(k) { + case FE_TOK_ORELSE: case FE_TOK_CATCH:return 1; + case FE_TOK_OR_KW:return 2; case FE_TOK_AND_KW:return 3; + case FE_TOK_EQEQ: case FE_TOK_NE: case FE_TOK_LT: case FE_TOK_LE: case FE_TOK_GT: case FE_TOK_GE:return 4; + case FE_TOK_OR:return 5; case FE_TOK_XOR:return 6; case FE_TOK_AND:return 7; + case FE_TOK_SHL: case FE_TOK_SHR:return 8; + case FE_TOK_PLUS: case FE_TOK_MINUS: case FE_TOK_PLUS_WRAP: case FE_TOK_MINUS_WRAP:return 9; + case FE_TOK_STAR: case FE_TOK_SLASH: case FE_TOK_PERCENT: case FE_TOK_STAR_WRAP:return 10; + default:return 0; + } +} +static FeNode *primary(FeParser *p) +{ + FeToken t=p->current; FeNode *n; + if(is(p,FE_TOK_INT)||is(p,FE_TOK_CHAR)||is(p,FE_TOK_STRING)||is(p,FE_TOK_TRUE)||is(p,FE_TOK_FALSE)||is(p,FE_TOK_NULL)||is(p,FE_TOK_UNDEFINED)) {next(p);return toknode(p,FE_N_LITERAL,t);} + if(is_name(p) || is(p,FE_TOK_ERROR_KW)) { + next(p); n=toknode(p,FE_N_IDENT,t); + if(is(p,FE_TOK_LBRACE)) { + FeNode *s=toknode(p,FE_N_STRUCT_INIT,t); next(p); + while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)) { FeNode *f; + if(!is_name(p)){error(p,"expected field name");recover(p);break;} f=toknode(p,FE_N_FIELD,p->current);next(p);want(p,FE_TOK_COLON,"expected ':' after field");f->a=expr(p,0);fe_node_add(s,f);if(!eat(p,FE_TOK_COMMA))break; + } want(p,FE_TOK_RBRACE,"expected '}' in struct literal"); return s; + } + return n; + } + if(eat(p,FE_TOK_LPAREN)) { n=expr(p,0); want(p,FE_TOK_RPAREN,"expected ')'"); return n; } + if(eat(p,FE_TOK_AT)) { + FeToken name=p->current; if(!is_name(p)){error(p,"expected builtin name after '@'");return fe_node(p->ast,FE_N_ERROR_NODE,t.loc,"builtin",7);} next(p); + n=toknode(p,FE_N_CALL,name); n->text=fe_arena_strdup(&p->ast->arena,name.begin-1,name.length+1); + if(eat(p,FE_TOK_LPAREN)){while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)){fe_node_add(n,expr(p,0));if(!eat(p,FE_TOK_COMMA))break;}want(p,FE_TOK_RPAREN,"expected ')' after builtin");} + return n; + } + error(p,"expected expression"); next(p); return fe_node(p->ast,FE_N_ERROR_NODE,t.loc,"expression",10); +} +static FeNode *postfix(FeParser *p) +{ + FeNode *n=primary(p); + for(;;) { + FeToken t=p->current; FeNode *m; + if(eat(p,FE_TOK_LPAREN)) { m=toknode(p,FE_N_CALL,t); m->a=n; while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)){fe_node_add(m,expr(p,0));if(!eat(p,FE_TOK_COMMA))break;} want(p,FE_TOK_RPAREN,"expected ')' after call"); n=m; } + else if(eat(p,FE_TOK_LBRACKET)) { m=toknode(p,FE_N_INDEX,t);m->a=n;m->b=expr(p,0);if(eat(p,FE_TOK_DOTDOT)){m->c=expr(p,0);}want(p,FE_TOK_RBRACKET,"expected ']' after index");n=m; } + else if(eat(p,FE_TOK_DOT)) { m=toknode(p,FE_N_MEMBER,t);m->a=n;if(is_name(p)){m->b=toknode(p,FE_N_IDENT,p->current);next(p);}else if(eat(p,FE_TOK_QUESTION)){m->text=fe_arena_strdup(&p->ast->arena,".?",2);}else error(p,"expected member name");n=m; } + else if(eat(p,FE_TOK_AS)) { m=toknode(p,FE_N_TYPE,t);m->a=n;m->b=type(p);n=m; } + else break; + } + return n; +} +static FeNode *expr(FeParser *p, int minprec) +{ + FeToken t=p->current; FeNode *left,*n; int prec; + if(is(p,FE_TOK_MINUS)||is(p,FE_TOK_NOT)||is(p,FE_TOK_XOR)||is(p,FE_TOK_AND)||is(p,FE_TOK_STAR)||is(p,FE_TOK_TRY)) { next(p); n=toknode(p,FE_N_UNARY,t); n->a=expr(p,11); left=n; } + else left=postfix(p); + for(;;) { t=p->current;prec=precedence(t.kind);if(prec<=minprec)break;next(p);n=toknode(p,FE_N_BINARY,t);n->a=left;if(t.kind==FE_TOK_CATCH && eat(p,FE_TOK_OR)){if(is_name(p))n->b=toknode(p,FE_N_IDENT,p->current),next(p);else error(p,"expected catch binding");want(p,FE_TOK_OR,"expected '|' after catch binding");n->c=block(p);}else n->b=expr(p,prec);left=n; } + return left; +} + +static FeNode *params(FeParser *p) +{ + FeNode *list=fe_node(p->ast,FE_N_BLOCK,p->current.loc,"params",6); + want(p,FE_TOK_LPAREN,"expected '(' after function name"); + while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)) { FeToken t=p->current; FeNode *q; + if(eat(p,FE_TOK_COMPTIME)) t=p->previous; + if(!is_name(p)){error(p,"expected parameter name");recover(p);break;} q=toknode(p,FE_N_PARAM,t);next(p);want(p,FE_TOK_COLON,"expected ':' in parameter");q->a=type(p);fe_node_add(list,q);if(!eat(p,FE_TOK_COMMA))break; + } + want(p,FE_TOK_RPAREN,"expected ')' after parameters"); return list; +} +static FeNode *fn_decl(FeParser *p, int pub, int external, int interrupt, int interrupt_safe) +{ + FeToken t=p->current, name; FeNode *n; + (void)pub; (void)external; (void)interrupt; (void)interrupt_safe; + want(p,FE_TOK_FN,"expected 'fn'"); if(!is_name(p)){error(p,"expected function name");return fe_node(p->ast,FE_N_ERROR_NODE,t.loc,"fn",2);} + name=p->current; n=toknode(p,FE_N_FN,t); n->text=fe_arena_strdup(&p->ast->arena,name.begin,name.length); next(p); n->a=params(p); if(eat(p,FE_TOK_ARROW)) n->b=type(p); if(eat(p,FE_TOK_SEMI)) return n; n->c=block(p); return n; +} +static FeNode *field(FeParser *p) +{ + FeToken t=p->current; FeNode *n; + if(!is_name(p)){error(p,"expected field name");recover(p);return 0;} next(p);n=toknode(p,FE_N_FIELD,t);want(p,FE_TOK_COLON,"expected ':' after field");n->a=type(p);if(!eat(p,FE_TOK_COMMA) && !is(p,FE_TOK_RBRACE)) error(p,"expected ',' after field");return n; +} +static FeNode *decl(FeParser *p) +{ + int pub=0, external=0, interrupt=0, interrupt_safe=0, shared=0, atomic=0; FeToken t=p->current; FeNode *n; + (void)shared; (void)atomic; + if(eat(p,FE_TOK_PUB)) pub=1; + if(eat(p,FE_TOK_EXTERN)) { external=1; if(is(p,FE_TOK_STRING)) next(p); } + if(eat(p,FE_TOK_INTERRUPT)) interrupt=1; + if(eat(p,FE_TOK_INTERRUPT_SAFE)) interrupt_safe=1; + if(!is(p,FE_TOK_PACKED)) t=p->current; + if(is(p,FE_TOK_FN)) return fn_decl(p,pub,external,interrupt,interrupt_safe); + if(eat(p,FE_TOK_PACKED)) t=p->previous; + if(eat(p,FE_TOK_STRUCT)) { n=toknode(p,FE_N_STRUCT,t);if(!is_name(p)){error(p,"expected struct name");return n;}next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);if(eat(p,FE_TOK_LPAREN)){while(!is(p,FE_TOK_RPAREN)&&!is(p,FE_TOK_EOF)){fe_node_add(n,type(p));if(!eat(p,FE_TOK_COMMA))break;}want(p,FE_TOK_RPAREN,"expected ')' after generic parameters");}want(p,FE_TOK_LBRACE,"expected '{' in struct");while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)){if(is(p,FE_TOK_PUB))next(p);if(is(p,FE_TOK_FN))fe_node_add(n,fn_decl(p,0,0,0,0));else fe_node_add(n,field(p));}want(p,FE_TOK_RBRACE,"expected '}' after struct");return n; } + if(eat(p,FE_TOK_ENUM)) { n=toknode(p,FE_N_ENUM,t);if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected enum name");want(p,FE_TOK_LBRACE,"expected '{' in enum");while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)){FeNode *v=toknode(p,FE_N_VARIANT,p->current);if(is_name(p))next(p);else{error(p,"expected variant name");recover(p);break;}if(eat(p,FE_TOK_LPAREN)){v->a=type(p);want(p,FE_TOK_RPAREN,"expected ')' in variant");}else if(eat(p,FE_TOK_LBRACE)){while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF))fe_node_add(v,field(p));want(p,FE_TOK_RBRACE,"expected '}' in variant");}fe_node_add(n,v);if(!eat(p,FE_TOK_COMMA))break;}want(p,FE_TOK_RBRACE,"expected '}' after enum");return n; } + if(eat(p,FE_TOK_ERROR_KW)) { n=toknode(p,FE_N_ERROR_DECL,t);if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected error name");want(p,FE_TOK_LBRACE,"expected '{' in error declaration");while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)){FeNode *v=toknode(p,FE_N_VARIANT,p->current);if(is_name(p))next(p);else{error(p,"expected error member");recover(p);break;}want(p,FE_TOK_EQ,"expected '=' in error member");v->a=expr(p,0);want(p,FE_TOK_COMMA,"expected ',' in error declaration");fe_node_add(n,v);}want(p,FE_TOK_RBRACE,"expected '}' after error");return n; } + if(eat(p,FE_TOK_SHARED)) { shared=1; if(eat(p,FE_TOK_ATOMIC)) atomic=1; if(!is(p,FE_TOK_VAR)) error(p,"expected 'var' after shared"); } + if(is(p,FE_TOK_CONST)||is(p,FE_TOK_STATIC)||is(p,FE_TOK_VAR)) { FeTokKind kk=p->current.kind;next(p);n=toknode(p,kk==FE_TOK_CONST?FE_N_CONST:FE_N_GLOBAL,t);if(is_name(p)){next(p);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected declaration name");if(eat(p,FE_TOK_COLON))n->a=type(p);want(p,FE_TOK_EQ,"expected '=' in declaration");n->b=expr(p,0);want(p,FE_TOK_SEMI,"expected ';' after declaration");return n; } + error(p,"expected declaration"); recover(p); return 0; +} + +static FeNode *block(FeParser *p) +{ + FeToken t=p->current; FeNode *n=toknode(p,FE_N_BLOCK,t);want(p,FE_TOK_LBRACE,"expected '{'");while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)){FeNode *s=statement(p);if(s)fe_node_add(n,s);}want(p,FE_TOK_RBRACE,"expected '}'");return n; +} +static FeNode *statement(FeParser *p) +{ + FeToken t=p->current; FeNode *n,*e; + if(is(p,FE_TOK_LBRACE)) return block(p); + if(eat(p,FE_TOK_LET)) { n=toknode(p,FE_N_LET,t);if(is_name(p))next(p);else error(p,"expected variable name");if(eat(p,FE_TOK_COLON))n->a=type(p);want(p,FE_TOK_EQ,"expected '=' in let");n->b=expr(p,0);want(p,FE_TOK_SEMI,"expected ';'");return n; } + if(eat(p,FE_TOK_VAR)) { n=toknode(p,FE_N_VAR,t);if(is_name(p))next(p);else error(p,"expected variable name");if(eat(p,FE_TOK_COLON))n->a=type(p);if(eat(p,FE_TOK_EQ))n->b=expr(p,0);want(p,FE_TOK_SEMI,"expected ';'");return n; } + if(eat(p,FE_TOK_CONST)) { n=toknode(p,FE_N_CONST,t);if(is_name(p))next(p);else error(p,"expected constant name");if(eat(p,FE_TOK_COLON))n->a=type(p);want(p,FE_TOK_EQ,"expected '=' in const");n->b=expr(p,0);want(p,FE_TOK_SEMI,"expected ';'");return n; } + if(eat(p,FE_TOK_IF)) { n=toknode(p,FE_N_IF,t);if(eat(p,FE_TOK_LET)){n->text=fe_arena_strdup(&p->ast->arena,"if let",6);if(is_name(p))next(p);if(eat(p,FE_TOK_LPAREN)){if(is_name(p))next(p);want(p,FE_TOK_RPAREN,"expected ')' in if let pattern");}want(p,FE_TOK_EQ,"expected '=' in if let");}n->a=expr(p,0);n->b=block(p);if(eat(p,FE_TOK_ELSE))n->c=is(p,FE_TOK_IF)?statement(p):block(p);return n; } + if(eat(p,FE_TOK_COMPTIME)) { n=toknode(p,FE_N_IF,t);want(p,FE_TOK_IF,"expected 'if' after comptime");n->text=fe_arena_strdup(&p->ast->arena,"comptime if",11);n->a=expr(p,0);n->b=block(p);if(eat(p,FE_TOK_ELSE))n->c=is(p,FE_TOK_IF)?statement(p):block(p);return n; } + if(eat(p,FE_TOK_WHILE)) {n=toknode(p,FE_N_WHILE,t);n->a=expr(p,0);n->b=block(p);return n;} + if(eat(p,FE_TOK_FOR)) {n=toknode(p,FE_N_FOR,t);if(is_name(p))next(p);else error(p,"expected loop variable");if(eat(p,FE_TOK_COMMA)){if(is_name(p))next(p);else error(p,"expected second loop variable");}want(p,FE_TOK_IN,"expected 'in' in for");n->a=expr(p,0);if(eat(p,FE_TOK_DOTDOT))n->c=expr(p,0);n->b=block(p);return n;} + if(eat(p,FE_TOK_MATCH)) { n=toknode(p,FE_N_MATCH,t);n->a=expr(p,0);want(p,FE_TOK_LBRACE,"expected '{' after match expression");while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF)){FeNode *arm=toknode(p,FE_N_ARM,p->current);if(is_name(p)||is(p,FE_TOK_INT)||is(p,FE_TOK_CHAR)||is(p,FE_TOK_NULL)||is(p,FE_TOK_TRUE)||is(p,FE_TOK_FALSE)||is(p,FE_TOK_IDENT)){arm->text=fe_arena_strdup(&p->ast->arena,p->current.begin,p->current.length);next(p);}else{error(p,"expected match pattern");recover(p);continue;}while(is(p,FE_TOK_LPAREN)||is(p,FE_TOK_LBRACE)){FeTokKind close=is(p,FE_TOK_LPAREN)?FE_TOK_RPAREN:FE_TOK_RBRACE;next(p);while(!is(p,close)&&!is(p,FE_TOK_EOF))next(p);want(p,close,"expected end of match pattern");}want(p,FE_TOK_FATARROW,"expected '=>' in match arm");if(is(p,FE_TOK_LBRACE))arm->a=block(p);else{arm->a=expr(p,0);want(p,FE_TOK_SEMI,"expected ';' in match arm");}fe_node_add(n,arm);}want(p,FE_TOK_RBRACE,"expected '}' after match");return n;} + if(eat(p,FE_TOK_RETURN)) {n=toknode(p,FE_N_RETURN,t);if(!is(p,FE_TOK_SEMI))n->a=expr(p,0);want(p,FE_TOK_SEMI,"expected ';' after return");return n;} + if(eat(p,FE_TOK_BREAK)){n=toknode(p,FE_N_BREAK,t);want(p,FE_TOK_SEMI,"expected ';'");return n;} + if(eat(p,FE_TOK_CONTINUE)){n=toknode(p,FE_N_CONTINUE,t);want(p,FE_TOK_SEMI,"expected ';'");return n;} + if(eat(p,FE_TOK_DEFER)){n=toknode(p,FE_N_DEFER,t);n->a=block(p);return n;} + if(eat(p,FE_TOK_UNSAFE)){n=toknode(p,FE_N_UNSAFE,t);n->a=block(p);return n;} + if(eat(p,FE_TOK_CRITICAL)){n=toknode(p,FE_N_UNSAFE,t);n->text=fe_arena_strdup(&p->ast->arena,"critical",8);n->a=block(p);return n;} + if(eat(p,FE_TOK_ASM)){n=toknode(p,FE_N_ASM,t);want(p,FE_TOK_LBRACE,"expected '{' after asm");while(!is(p,FE_TOK_RBRACE)&&!is(p,FE_TOK_EOF))next(p);want(p,FE_TOK_RBRACE,"expected '}' after asm");return n;} + e=expr(p,0); if(is(p,FE_TOK_EQ)||is(p,FE_TOK_PLUS_EQ)||is(p,FE_TOK_MINUS_EQ)||is(p,FE_TOK_STAR_EQ)||is(p,FE_TOK_SLASH_EQ)||is(p,FE_TOK_PERCENT_EQ)||is(p,FE_TOK_AND_EQ)||is(p,FE_TOK_OR_EQ)||is(p,FE_TOK_XOR_EQ)||is(p,FE_TOK_SHL_EQ)||is(p,FE_TOK_SHR_EQ)){n=toknode(p,FE_N_ASSIGN,p->current);n->a=e;next(p);n->b=expr(p,0);}else{n=toknode(p,FE_N_EXPR_STMT,t);n->a=e;}want(p,FE_TOK_SEMI,"expected ';' after statement");return n; +} + +FeNode *fe_parse_unit(FeParser *p) +{ + FeToken t=p->current, name; FeNode *root; + if(!eat(p,FE_TOK_UNIT)){error(p,"source must start with 'unit'");return fe_node(p->ast,FE_N_ERROR_NODE,t.loc,"unit",4);} + root=toknode(p,FE_N_UNIT,t);if(is_name(p)){name=p->current;root->text=fe_arena_strdup(&p->ast->arena,name.begin,name.length);next(p);}else error(p,"expected unit name");want(p,FE_TOK_SEMI,"expected ';' after unit name"); + while(eat(p,FE_TOK_IMPORT)){FeToken it=p->previous;FeNode *i=toknode(p,FE_N_IMPORT,it);if(is_name(p)){next(p);i->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}else error(p,"expected import name");want(p,FE_TOK_SEMI,"expected ';' after import");fe_node_add(root,i);} + while(!is(p,FE_TOK_EOF)){FeNode *d=decl(p);if(d)fe_node_add(root,d);} + return root; +} diff --git a/fec/src/parser.h b/fec/src/parser.h new file mode 100644 index 0000000..a0d5ef7 --- /dev/null +++ b/fec/src/parser.h @@ -0,0 +1,17 @@ +#ifndef FE_PARSER_H +#define FE_PARSER_H + +#include "ast.h" + +typedef struct FeParser { + FeLexer lexer; + FeToken current; + FeToken previous; + FeAst *ast; + FeDiags *diags; +} FeParser; + +void fe_parser_init(FeParser *p, FeAst *ast, const char *src, unsigned long length, const char *file, FeDiags *d); +FeNode *fe_parse_unit(FeParser *p); + +#endif diff --git a/fec/std/core.fe b/fec/std/core.fe new file mode 100644 index 0000000..38a3c58 --- /dev/null +++ b/fec/std/core.fe @@ -0,0 +1,5 @@ +unit core; + +pub error Error { Invalid = 1, Io = 2, } +pub fn panic(msg: str, file: str, line: u32) { } +pub fn assert(ok: bool) { } diff --git a/fec/std/fmt.fe b/fec/std/fmt.fe new file mode 100644 index 0000000..460cc0b --- /dev/null +++ b/fec/std/fmt.fe @@ -0,0 +1,4 @@ +unit fmt; +pub fn write_str(w: &mut io.Writer, s: str) -> !void; +pub fn write_int_i32(w: &mut io.Writer, v: i32) -> !void; +pub fn write_bool(w: &mut io.Writer, v: bool) -> !void; diff --git a/fec/std/io.fe b/fec/std/io.fe new file mode 100644 index 0000000..2f4f983 --- /dev/null +++ b/fec/std/io.fe @@ -0,0 +1,9 @@ +unit io; +pub struct Writer { + ctx: *void, + write_fn: fn(*void, []u8) -> !usize, +} +pub struct File { + handle: u16, + pub fn close(self: &mut Self) { } +} diff --git a/fec/std/list.fe b/fec/std/list.fe new file mode 100644 index 0000000..d414df7 --- /dev/null +++ b/fec/std/list.fe @@ -0,0 +1,6 @@ +unit list; +pub struct List(T) { + items: ^[]T, + len: usize, + pub fn at(self: &Self, i: usize) -> &T; +} diff --git a/fec/std/map.fe b/fec/std/map.fe new file mode 100644 index 0000000..5e238c6 --- /dev/null +++ b/fec/std/map.fe @@ -0,0 +1,4 @@ +unit map; +pub struct Map(K, V) { + len: usize, +} diff --git a/fec/std/mem.fe b/fec/std/mem.fe new file mode 100644 index 0000000..cec54f5 --- /dev/null +++ b/fec/std/mem.fe @@ -0,0 +1,11 @@ +unit mem; + +pub fn create(T: type) -> !^T; +pub fn destroy(p: *void); +pub fn copy(dst: []u8, src: []u8); +pub struct Arena { + ptr: *void, + pub fn init() -> Arena { return Arena{ ptr: null }; } + pub fn reset(self: &mut Self) { } + pub fn drop(self: &mut Self) { } +} diff --git a/fec/std/str.fe b/fec/std/str.fe new file mode 100644 index 0000000..4d5d7f0 --- /dev/null +++ b/fec/std/str.fe @@ -0,0 +1,3 @@ +unit str; +pub fn eq(a: str, b: str) -> bool; +pub fn trim(s: str) -> str; diff --git a/fec/std/sys.fe b/fec/std/sys.fe new file mode 100644 index 0000000..d9003a8 --- /dev/null +++ b/fec/std/sys.fe @@ -0,0 +1,2 @@ +unit sys; +pub fn exit(code: u16); diff --git a/fec/test-dos.bat b/fec/test-dos.bat new file mode 100644 index 0000000..36935d8 --- /dev/null +++ b/fec/test-dos.bat @@ -0,0 +1,51 @@ +@echo off +rem FreeDOS smoke tests. All work happens on the writable C: drive. +C: +cd \FEC +if exist TEST.OK del TEST.OK +if exist TEST.FAIL del TEST.FAIL +call C:\FEC\BUILD.BAT +if not exist BUILD.OK goto test_fail + +fec.exe --dump-ast TESTS\PASS\BASIC.FE > nul +if errorlevel 1 goto test_fail +fec.exe --dump-ast TESTS\PASS\LITERALS.FE > nul +if errorlevel 1 goto test_fail +fec.exe --dump-ast TESTS\PASS\KEYWOR.FE > nul +if errorlevel 1 goto test_fail +fec.exe --dump-ast TESTS\PASS\V012-F.FE > nul +if errorlevel 1 goto test_fail + +fec.exe --dump-ast STD\CORE.FE > nul +if errorlevel 1 goto test_fail +fec.exe --dump-ast STD\FMT.FE > nul +if errorlevel 1 goto test_fail +fec.exe --dump-ast STD\IO.FE > nul +if errorlevel 1 goto test_fail +fec.exe --dump-ast STD\LIST.FE > nul +if errorlevel 1 goto test_fail +fec.exe --dump-ast STD\MAP.FE > nul +if errorlevel 1 goto test_fail +fec.exe --dump-ast STD\MEM.FE > nul +if errorlevel 1 goto test_fail +fec.exe --dump-ast STD\STR.FE > nul +if errorlevel 1 goto test_fail +fec.exe --dump-ast STD\SYS.FE > nul +if errorlevel 1 goto test_fail + +fec.exe --dump-ast TESTS\FAIL\MISSIN.FE > nul +if not errorlevel 1 goto test_fail +fec.exe --dump-ast TESTS\FAIL\UNCLOS.FE > nul +if not errorlevel 1 goto test_fail +fec.exe --dump-ast TESTS\FAIL\LOGICA.FE > nul +if not errorlevel 1 goto test_fail + +echo OK>TEST.OK +cd C:\FEC +goto test_done + +:test_fail +echo FAIL>TEST.FAIL +verify other 2>nul + +:test_done diff --git a/fec/tests/fail/logical-symbols.fe b/fec/tests/fail/logical-symbols.fe new file mode 100644 index 0000000..a38bcc6 --- /dev/null +++ b/fec/tests/fail/logical-symbols.fe @@ -0,0 +1,3 @@ +// ERROR:logical operator +unit old_logic; +fn main() { let x = true && false; } diff --git a/fec/tests/fail/missing-semi.fe b/fec/tests/fail/missing-semi.fe new file mode 100644 index 0000000..c767019 --- /dev/null +++ b/fec/tests/fail/missing-semi.fe @@ -0,0 +1,3 @@ +// ERROR:expected ';' +unit broken; +fn main() { let x: i32 = 1 } diff --git a/fec/tests/fail/unclosed-comment.fe b/fec/tests/fail/unclosed-comment.fe new file mode 100644 index 0000000..fe95c5a --- /dev/null +++ b/fec/tests/fail/unclosed-comment.fe @@ -0,0 +1,3 @@ +// ERROR:unterminated block comment +unit broken; +/* no ending delimiter diff --git a/fec/tests/pass/basic.fe b/fec/tests/pass/basic.fe new file mode 100644 index 0000000..86d4062 --- /dev/null +++ b/fec/tests/pass/basic.fe @@ -0,0 +1,25 @@ +unit basic; +import core; + +/* outer comment /* nested comment */ still active */ +const LIMIT: u16 = 1_000; +pub struct Point { + pub x: i32, + y: i32, + pub fn new(x: i32, y: i32) -> Point { return Point{ x: x, y: y }; } + pub fn shift(self: &mut Self, dx: i32) { self.x += dx; } +} +pub enum Shape { + Empty, + Circle(i32), + Rect{ w: i32, h: i32 }, +} +pub error IoError { NotFound = 1, Denied = 2, } + +pub fn main() -> !void { + let p: Point = Point{ x: 1, y: 2 }; + if p.x > 0 and true { p.shift(1); } else { p.x = 0; } + while p.x < 10 { p.x += 1; if p.x == 5 { continue; } } + for i, x in p.x..10 { let _n: usize = i; let _q = x; } + return; +} diff --git a/fec/tests/pass/keywords-and-builtins.fe b/fec/tests/pass/keywords-and-builtins.fe new file mode 100644 index 0000000..21b231d --- /dev/null +++ b/fec/tests/pass/keywords-and-builtins.fe @@ -0,0 +1,9 @@ +unit keywords_and_builtins; + +pub fn demo() { + let a = true and not false; + let b = a or false; + @print("selected branch"); + let p = @as_far_fn(handler); + @call_far(p); +} diff --git a/fec/tests/pass/literals.fe b/fec/tests/pass/literals.fe new file mode 100644 index 0000000..775e9be --- /dev/null +++ b/fec/tests/pass/literals.fe @@ -0,0 +1,12 @@ +unit literals; +const A: u32 = 0xFF; +const B: u16 = 0b1010; +const C: u16 = 0o17; +const D: u32 = 1_000_000; +pub fn strings() { + let a = "hello\\nworld"; + let b = '\x41'; + let c = true; + let d = null; + let e = a orelse "fallback"; +} diff --git a/fec/tests/pass/v012-forms.fe b/fec/tests/pass/v012-forms.fe new file mode 100644 index 0000000..db3d4b5 --- /dev/null +++ b/fec/tests/pass/v012-forms.fe @@ -0,0 +1,20 @@ +unit v012_forms; + +shared atomic var ticks: u16 = 0; +packed struct Packet { + tag: u8, + value: u16, +} +interrupt_safe fn poll() { } +interrupt fn timer() { } +fn invoke(p: far fn()) { } + +pub fn demo() { + var count = undefined; + count = 1; + critical { ticks += 1; } + let x = true and not false or false; + let y = x orelse true; + let e = error.NotFound; + @call_far(@as_far_fn(timer)); +} diff --git a/fec/tests/run-tests.sh b/fec/tests/run-tests.sh new file mode 100644 index 0000000..6b4eef7 --- /dev/null +++ b/fec/tests/run-tests.sh @@ -0,0 +1,20 @@ +#!/bin/sh +set -eu +root=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd) +ok=0 +for f in "$root"/std/*.fe; do + [ -f "$f" ] || continue + "$root"/fec --dump-ast "$f" >/dev/null || { echo "FAIL: $f"; exit 1; } + ok=$((ok+1)) +done +for f in "$root"/tests/pass/*.fe; do + [ -f "$f" ] || continue + "$root"/fec --dump-ast "$f" >/dev/null || { echo "FAIL: $f"; exit 1; } + ok=$((ok+1)) +done +for f in "$root"/tests/fail/*.fe; do + [ -f "$f" ] || continue + if "$root"/fec --dump-ast "$f" >/dev/null 2>/dev/null; then echo "FAIL (accepted): $f"; exit 1; fi + ok=$((ok+1)) +done +echo "M1 tests: $ok cases passed" diff --git a/fec/vm-m1.bat b/fec/vm-m1.bat new file mode 100644 index 0000000..241d530 --- /dev/null +++ b/fec/vm-m1.bat @@ -0,0 +1,86 @@ +@echo off +rem D: is the read-only exchange volume. Stage everything before running DOS tools. +if not exist C:\FEC md C:\FEC +if not exist C:\FEC\SRC md C:\FEC\SRC +if not exist C:\FEC\STD md C:\FEC\STD +if not exist C:\FEC\TESTS md C:\FEC\TESTS +if not exist C:\FEC\TESTS\PASS md C:\FEC\TESTS\PASS +if not exist C:\FEC\TESTS\FAIL md C:\FEC\TESTS\FAIL +if exist C:\FEC\VM.FAIL del C:\FEC\VM.FAIL + +copy D:\FEC\BUILD-~1.BAT C:\FEC\BUILD.BAT > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TEST-DOS.BAT C:\FEC\TEST-DOS.BAT > nul +if errorlevel 1 goto stage_fail + +copy D:\FEC\SRC\ARENA.C C:\FEC\SRC\ARENA.C > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\SRC\ARENA.H C:\FEC\SRC\ARENA.H > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\SRC\DIAG.C C:\FEC\SRC\DIAG.C > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\SRC\DIAG.H C:\FEC\SRC\DIAG.H > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\SRC\LEXER.C C:\FEC\SRC\LEXER.C > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\SRC\LEXER.H C:\FEC\SRC\LEXER.H > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\SRC\AST.C C:\FEC\SRC\AST.C > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\SRC\AST.H C:\FEC\SRC\AST.H > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\SRC\PARSER.C C:\FEC\SRC\PARSER.C > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\SRC\PARSER.H C:\FEC\SRC\PARSER.H > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\SRC\DRIVER.C C:\FEC\SRC\DRIVER.C > nul +if errorlevel 1 goto stage_fail + +copy D:\FEC\STD\CORE.FE C:\FEC\STD\CORE.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\STD\FMT.FE C:\FEC\STD\FMT.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\STD\IO.FE C:\FEC\STD\IO.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\STD\LIST.FE C:\FEC\STD\LIST.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\STD\MAP.FE C:\FEC\STD\MAP.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\STD\MEM.FE C:\FEC\STD\MEM.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\STD\STR.FE C:\FEC\STD\STR.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\STD\SYS.FE C:\FEC\STD\SYS.FE > nul +if errorlevel 1 goto stage_fail + +copy D:\FEC\TESTS\PASS\BASIC.FE C:\FEC\TESTS\PASS\BASIC.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\PASS\LITERALS.FE C:\FEC\TESTS\PASS\LITERALS.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\PASS\KEYWOR~1.FE C:\FEC\TESTS\PASS\KEYWOR.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\PASS\V012-F~1.FE C:\FEC\TESTS\PASS\V012-F.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\FAIL\MISSIN~1.FE C:\FEC\TESTS\FAIL\MISSIN.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\FAIL\UNCLOS~1.FE C:\FEC\TESTS\FAIL\UNCLOS.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\FAIL\LOGICA~1.FE C:\FEC\TESTS\FAIL\LOGICA.FE > nul +if errorlevel 1 goto stage_fail + +call C:\FEC\TEST-DOS.BAT +if exist C:\FEC\TEST.OK goto vm_success +echo FAIL>C:\FEC\VM.FAIL +verify other 2>nul +goto stage_done + +:vm_success +cd C:\FEC +goto stage_done + +:stage_fail +echo FAIL>C:\FEC\STAGE.FAIL +echo FAIL>C:\FEC\VM.FAIL +verify other 2>nul + +:stage_done