feat: implement M1 Ferro frontend

This commit is contained in:
2026-08-16 07:10:53 +09:00
parent 990068f424
commit 005056a5ea
32 changed files with 986 additions and 0 deletions
+23
View File
@@ -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
+29
View File
@@ -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
+56
View File
@@ -0,0 +1,56 @@
#include "arena.h"
#include <stdlib.h>
#include <string.h>
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;
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef FE_ARENA_H
#define FE_ARENA_H
#include <stddef.h>
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
+39
View File
@@ -0,0 +1,39 @@
#include "ast.h"
#include <stdio.h>
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];
}
+41
View File
@@ -0,0 +1,41 @@
#ifndef FE_AST_H
#define FE_AST_H
#include "arena.h"
#include "lexer.h"
#include <stdio.h>
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
+20
View File
@@ -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 : "<source>", 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 : "<source>", 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 : "<source>", loc.line, loc.col, msg);
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef FE_DIAG_H
#define FE_DIAG_H
#include <stdio.h>
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
+25
View File
@@ -0,0 +1,25 @@
#include "parser.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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<argc;i++) { if(strcmp(argv[i],"--dump-ast")==0) dump=1; else if(argv[i][0]!='-') file=argv[i]; else if(strncmp(argv[i],"--target=",9)==0 || strncmp(argv[i],"--model=",8)==0 || strcmp(argv[i],"--no-checks")==0 || strcmp(argv[i],"--emit-c")==0 || strcmp(argv[i],"--strip-error-names")==0) { } 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;
}
+170
View File
@@ -0,0 +1,170 @@
#include "lexer.h"
#include <ctype.h>
#include <string.h>
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";
}
}
+50
View File
@@ -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
+201
View File
@@ -0,0 +1,201 @@
#include "parser.h"
#include <string.h>
#include <stdio.h>
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;
}
+17
View File
@@ -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
+5
View File
@@ -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) { }
+4
View File
@@ -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;
+9
View File
@@ -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) { }
}
+6
View File
@@ -0,0 +1,6 @@
unit list;
pub struct List(T) {
items: ^[]T,
len: usize,
pub fn at(self: &Self, i: usize) -> &T;
}
+4
View File
@@ -0,0 +1,4 @@
unit map;
pub struct Map(K, V) {
len: usize,
}
+11
View File
@@ -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) { }
}
+3
View File
@@ -0,0 +1,3 @@
unit str;
pub fn eq(a: str, b: str) -> bool;
pub fn trim(s: str) -> str;
+2
View File
@@ -0,0 +1,2 @@
unit sys;
pub fn exit(code: u16);
+51
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
// ERROR:logical operator
unit old_logic;
fn main() { let x = true && false; }
+3
View File
@@ -0,0 +1,3 @@
// ERROR:expected ';'
unit broken;
fn main() { let x: i32 = 1 }
+3
View File
@@ -0,0 +1,3 @@
// ERROR:unterminated block comment
unit broken;
/* no ending delimiter
+25
View File
@@ -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;
}
+9
View File
@@ -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);
}
+12
View File
@@ -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";
}
+20
View File
@@ -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));
}
+20
View File
@@ -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"
+86
View File
@@ -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