From da78a615fbfcc0b32ce0c74e954bd84f7efe3ac5 Mon Sep 17 00:00:00 2001 From: Sebastian Jeong Date: Sun, 16 Aug 2026 07:40:50 +0900 Subject: [PATCH] feat: add M2 type checking and C emission --- fec/Makefile | 2 +- fec/build-dos.bat | 27 +- fec/src/ast.c | 2 +- fec/src/ast.h | 4 + fec/src/check.c | 515 ++++++++++++++++++++++++++++++++++ fec/src/check.h | 19 ++ fec/src/driver.c | 14 +- fec/src/emit_c.c | 280 ++++++++++++++++++ fec/src/emit_c.h | 16 ++ fec/src/parser.c | 4 +- fec/src/types.c | 50 ++++ fec/src/types.h | 31 ++ fec/test-dos.bat | 53 ++++ fec/tests/m2/bad-arity.fe | 9 + fec/tests/m2/bad-assign.fe | 7 + fec/tests/m2/bad-cast.fe | 6 + fec/tests/m2/bad-condition.fe | 6 + fec/tests/m2/bad-return.fe | 5 + fec/tests/m2/bad-types.fe | 9 + fec/tests/m2/bad-uninit.fe | 6 + fec/tests/m2/bad-unknown.fe | 5 + fec/tests/m2/bad-void.fe | 10 + fec/tests/m2/cast-while.fe | 11 + fec/tests/m2/hello.fe | 23 ++ fec/tests/m2/scopes.fe | 15 + fec/tests/run-tests.sh | 22 ++ fec/vm-m1.bat | 38 +++ 27 files changed, 1180 insertions(+), 9 deletions(-) create mode 100644 fec/src/check.c create mode 100644 fec/src/check.h create mode 100644 fec/src/emit_c.c create mode 100644 fec/src/emit_c.h create mode 100644 fec/src/types.c create mode 100644 fec/src/types.h create mode 100644 fec/tests/m2/bad-arity.fe create mode 100644 fec/tests/m2/bad-assign.fe create mode 100644 fec/tests/m2/bad-cast.fe create mode 100644 fec/tests/m2/bad-condition.fe create mode 100644 fec/tests/m2/bad-return.fe create mode 100644 fec/tests/m2/bad-types.fe create mode 100644 fec/tests/m2/bad-uninit.fe create mode 100644 fec/tests/m2/bad-unknown.fe create mode 100644 fec/tests/m2/bad-void.fe create mode 100644 fec/tests/m2/cast-while.fe create mode 100644 fec/tests/m2/hello.fe create mode 100644 fec/tests/m2/scopes.fe diff --git a/fec/Makefile b/fec/Makefile index 0891f8f..7f74ee8 100644 --- a/fec/Makefile +++ b/fec/Makefile @@ -1,7 +1,7 @@ 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 +SRC = src/arena.c src/diag.c src/lexer.c src/ast.c src/parser.c src/types.c src/check.c src/emit_c.c src/driver.c OBJ = $(SRC:.c=.o) .PHONY: all clean test dos-build diff --git a/fec/build-dos.bat b/fec/build-dos.bat index 510bbfa..42a1f5e 100644 --- a/fec/build-dos.bat +++ b/fec/build-dos.bat @@ -11,12 +11,37 @@ 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 types.obj del types.obj +if exist check.obj del check.obj +if exist emitc.obj del emitc.obj +if exist emit_c.obj del emit_c.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 +wcl -q -za -wx -bt=dos -ml -k32768 -c -fo=arena.obj src\arena.c +if errorlevel 1 goto build_fail +wcl -q -za -wx -bt=dos -ml -k32768 -c -fo=diag.obj src\diag.c +if errorlevel 1 goto build_fail +wcl -q -za -wx -bt=dos -ml -k32768 -c -fo=lexer.obj src\lexer.c +if errorlevel 1 goto build_fail +wcl -q -za -wx -bt=dos -ml -k32768 -c -fo=ast.obj src\ast.c +if errorlevel 1 goto build_fail +wcl -q -za -wx -bt=dos -ml -k32768 -c -fo=parser.obj src\parser.c +if errorlevel 1 goto build_fail +wcl -q -za -wx -bt=dos -ml -k32768 -c -fo=types.obj src\types.c +if errorlevel 1 goto build_fail +wcl -q -za -wx -bt=dos -ml -k32768 -c -fo=check.obj src\check.c +if errorlevel 1 goto build_fail +rem Use an unambiguous short object name for the emit_c source. +wcl -q -za -wx -bt=dos -ml -k32768 -c -fo=emitc.obj src\emit_c.c +if errorlevel 1 goto build_fail +wcl -q -za -wx -bt=dos -ml -k32768 -c -fo=driver.obj src\driver.c +if errorlevel 1 goto build_fail +rem DOS command lines are limited to roughly 126 characters. All stale objects +rem were removed above, so the wildcard contains exactly this build's objects. +wcl -q -za -wx -bt=dos -ml -k32768 -fe=fec.exe *.obj if errorlevel 1 goto build_fail if not exist fec.exe goto build_fail echo OK>BUILD.OK diff --git a/fec/src/ast.c b/fec/src/ast.c index 81b0e14..798c4ed 100644 --- a/fec/src/ast.c +++ b/fec/src/ast.c @@ -8,7 +8,7 @@ FeNode *fe_node(FeAst *a, FeNodeKind k, FeLoc loc, const char *text, unsigned lo 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; + n->a=n->b=n->c=n->children=n->next=0; n->cname=0; n->sem_type=0; return n; } void fe_node_add(FeNode *parent, FeNode *child) { diff --git a/fec/src/ast.h b/fec/src/ast.h index 3b26b02..535d4ce 100644 --- a/fec/src/ast.h +++ b/fec/src/ast.h @@ -15,6 +15,7 @@ typedef enum FeNodeKind { } FeNodeKind; typedef struct FeNode FeNode; +typedef struct FeType FeType; struct FeNode { FeNodeKind kind; FeLoc loc; @@ -24,6 +25,9 @@ struct FeNode { FeNode *c; FeNode *children; FeNode *next; + /* Semantic information filled by checking; kept out of AST dumps. */ + char *cname; + FeType *sem_type; }; typedef struct FeAst { diff --git a/fec/src/check.c b/fec/src/check.c new file mode 100644 index 0000000..536c149 --- /dev/null +++ b/fec/src/check.c @@ -0,0 +1,515 @@ +#include "check.h" +#include +#include + +typedef struct FeSym FeSym; +typedef struct FeScope FeScope; + +struct FeSym { + const char *name; + char *cname; + FeType *type; + FeNode *fn; + int mutable; + int initialized; +}; + +struct FeScope { + FeScope *parent; + FeSym *items; + unsigned count; + unsigned capacity; +}; + +typedef struct FeCheckerState { + FeCheck *c; + FeScope *scope; + FeScope *globals; + FeType *ret; +} FeCheckerState; + +static FeType *unknown(FeCheck *c) +{ + return fe_type_intern(&c->types, ""); +} + +static void err(FeCheck *c, FeLoc loc, const char *msg) +{ + fe_diag_error(c->diags, loc, msg); +} + +static int known(FeType *t) +{ + return t && t->kind != FE_TYPE_UNKNOWN && t->kind != FE_TYPE_ERROR; +} + +static int compatible(FeType *want, FeType *got, FeNode *value) +{ + if (fe_type_equal(want, got)) return 1; + if (!known(want) || !known(got)) return 1; + return fe_type_is_integer(want) && fe_type_is_integer(got) && value && + value->kind == FE_N_LITERAL && value->text && + value->text[0] != '\'' && value->text[0] != '"'; +} + +static FeType *node_type(FeCheck *c, FeNode *n) +{ + FeType *t; + if (!n) return unknown(c); + t = fe_type_from_ast(&c->types, n); + n->sem_type = t; + return t; +} + +static char *unit_cname(FeCheck *c, const char *name) +{ + char *u; + char *p; + unsigned long n; + u = c->ast->root && c->ast->root->text ? c->ast->root->text : "unit"; + n = (unsigned long)strlen("fe_") + (unsigned long)strlen(u) + + (unsigned long)strlen(name ? name : "name") + 2UL; + p = (char *)fe_arena_alloc(&c->ast->arena, n); + if (!p) return 0; + strcpy(p, "fe_"); + strcat(p, u); + strcat(p, "_"); + strcat(p, name ? name : "name"); + return p; +} + +static char *local_cname(FeCheck *c, const char *name) +{ + char number[24]; + char *p; + unsigned long n; + sprintf(number, "%u", c->local_serial++); + n = (unsigned long)strlen("fe_l_") + (unsigned long)strlen(name) + + (unsigned long)strlen(number) + 2UL; + p = (char *)fe_arena_alloc(&c->ast->arena, n); + if (!p) return 0; + strcpy(p, "fe_l_"); + strcat(p, name ? name : "local"); + strcat(p, "_"); + strcat(p, number); + return p; +} + +static FeScope *scope_new(FeCheckerState *s, FeScope *parent) +{ + FeScope *scope; + scope = (FeScope *)fe_arena_alloc(&s->c->ast->arena, sizeof(FeScope)); + if (!scope) { + err(s->c, s->c->ast->root->loc, "out of memory creating scope"); + return parent; + } + scope->parent = parent; + scope->items = 0; + scope->count = 0; + scope->capacity = 0; + return scope; +} + +static FeSym *find_current(FeScope *scope, const char *name) +{ + unsigned i; + if (!scope) return 0; + for (i = scope->count; i > 0; --i) + if (strcmp(scope->items[i - 1].name, name) == 0) + return &scope->items[i - 1]; + return 0; +} + +static FeSym *find_symbol(FeScope *scope, const char *name) +{ + FeSym *sym; + while (scope) { + sym = find_current(scope, name); + if (sym) return sym; + scope = scope->parent; + } + return 0; +} + +static FeSym *add_symbol(FeCheckerState *s, FeScope *scope, + const char *name, FeType *type, FeNode *fn, + int mutable, int initialized, char *cname, + FeNode *decl) +{ + FeSym *items; + unsigned capacity; + FeSym *sym; + if (!name) name = ""; + if (find_current(scope, name)) { + err(s->c, decl ? decl->loc : s->c->ast->root->loc, + "duplicate declaration in scope"); + return 0; + } + if (scope->count == scope->capacity) { + capacity = scope->capacity ? scope->capacity * 2U : 8U; + items = (FeSym *)fe_arena_alloc(&s->c->ast->arena, + capacity * sizeof(FeSym)); + if (!items) { + err(s->c, decl ? decl->loc : s->c->ast->root->loc, + "out of memory growing symbol scope"); + return 0; + } + if (scope->items) + memcpy(items, scope->items, scope->count * sizeof(FeSym)); + scope->items = items; + scope->capacity = capacity; + } + sym = &scope->items[scope->count++]; + sym->name = name; + sym->cname = cname; + sym->type = type; + sym->fn = fn; + sym->mutable = mutable; + sym->initialized = initialized; + if (decl) { + decl->cname = cname; + decl->sem_type = type; + } + return sym; +} + +void fe_check_init(FeCheck *c, FeAst *ast, FeDiags *diags, + unsigned pointer_bits) +{ + c->ast = ast; + c->diags = diags; + c->pointer_bits = pointer_bits; + c->local_serial = 0; + fe_types_init(&c->types, &ast->arena, pointer_bits); +} + +static FeType *check_expr(FeCheckerState *s, FeNode *n); + +static FeType *check_identifier(FeCheckerState *s, FeNode *n, int read) +{ + FeSym *sym; + sym = find_symbol(s->scope, n->text ? n->text : ""); + if (!sym) { + err(s->c, n->loc, "unknown name"); + return unknown(s->c); + } + n->cname = sym->cname; + n->sem_type = sym->type; + if (read && !sym->initialized && !sym->fn) + err(s->c, n->loc, "use of uninitialized variable"); + return sym->type; +} + +static FeType *check_expr(FeCheckerState *s, FeNode *n) +{ + FeCheck *c = s->c; + FeType *a; + FeType *b; + FeSym *sym; + FeNode *x; + FeNode *param; + FeNode *arg; + const char *op; + if (!n) return unknown(c); + if (n->kind == FE_N_IDENT) + return check_identifier(s, n, 1); + if (n->kind == FE_N_LITERAL) { + if (!n->text) return unknown(c); + if (strcmp(n->text, "true") == 0 || strcmp(n->text, "false") == 0) + a = fe_type_intern(&c->types, "bool"); + else if (n->text[0] == '\'') + a = fe_type_intern(&c->types, "u8"); + else if (n->text[0] == '"') + a = unknown(c); + else + a = fe_type_intern(&c->types, "i32"); + n->sem_type = a; + return a; + } + if (n->kind == FE_N_UNARY) { + a = check_expr(s, n->a); + op = n->text ? n->text : ""; + if (strcmp(op, "not") == 0) { + if (known(a) && a->kind != FE_TYPE_BOOL) + err(c, n->loc, "'not' requires bool"); + a = fe_type_intern(&c->types, "bool"); + } else if (strcmp(op, "-") == 0) { + if (known(a) && !fe_type_is_integer(a)) + err(c, n->loc, "unary '-' requires integer"); + } + n->sem_type = a; + return a; + } + if (n->kind == FE_N_TYPE && n->text && strcmp(n->text, "as") == 0) { + a = check_expr(s, n->a); + b = node_type(c, n->b); + if (b->kind == FE_TYPE_VOID) + err(c, n->loc, "cast target cannot be void"); + else if ((known(a) && !fe_type_is_integer(a)) || + (known(b) && !fe_type_is_integer(b))) + err(c, n->loc, "'as' requires integer types"); + n->sem_type = b; + return b; + } + if (n->kind == FE_N_BINARY) { + a = check_expr(s, n->a); + b = check_expr(s, n->b); + op = n->text ? n->text : ""; + if (strcmp(op, "and") == 0 || strcmp(op, "or") == 0) { + if ((known(a) && a->kind != FE_TYPE_BOOL) || + (known(b) && b->kind != FE_TYPE_BOOL)) + err(c, n->loc, "logical operator requires bool operands"); + a = fe_type_intern(&c->types, "bool"); + } else if (strcmp(op, "==") == 0 || strcmp(op, "!=") == 0 || + strcmp(op, "<") == 0 || strcmp(op, "<=") == 0 || + strcmp(op, ">") == 0 || strcmp(op, ">=") == 0) { + if (known(a) && known(b) && !fe_type_equal(a, b) && + !compatible(a, b, n->b) && !compatible(b, a, n->a)) + err(c, n->loc, "comparison operands have different types"); + a = fe_type_intern(&c->types, "bool"); + } else { + if ((known(a) && !fe_type_is_integer(a)) || + (known(b) && !fe_type_is_integer(b)) || + (known(a) && known(b) && !fe_type_equal(a, b) && + !compatible(a, b, n->b) && !compatible(b, a, n->a))) + err(c, n->loc, + "arithmetic operands must have the same integer type"); + } + n->sem_type = a; + return a; + } + if (n->kind == FE_N_CALL) { + if (n->a && n->a->kind == FE_N_IDENT) { + sym = find_symbol(s->scope, n->a->text ? n->a->text : ""); + if (!sym) { + err(c, n->loc, "unknown function"); + return unknown(c); + } + n->a->cname = sym->cname; + if (!sym->fn) { + err(c, n->loc, "name is not a function"); + return unknown(c); + } + param = sym->fn->a ? sym->fn->a->children : 0; + arg = n->children; + while (param && arg) { + a = check_expr(s, arg); + b = node_type(c, param->a); + if (!compatible(b, a, arg) && a->kind != FE_TYPE_UNKNOWN) + err(c, arg->loc, "argument type mismatch"); + param = param->next; + arg = arg->next; + } + if (param || arg) err(c, n->loc, "wrong number of arguments"); + a = sym->fn->b ? node_type(c, sym->fn->b) : + fe_type_intern(&c->types, "void"); + n->sem_type = a; + return a; + } + for (x = n->children; x; x = x->next) check_expr(s, x); + return unknown(c); + } + if (n->kind == FE_N_MEMBER) { + check_expr(s, n->a); + return unknown(c); + } + return unknown(c); +} + +static FeType *check_lvalue(FeCheckerState *s, FeNode *n, int read) +{ + FeSym *sym; + if (n && n->kind == FE_N_IDENT) { + sym = find_symbol(s->scope, n->text ? n->text : ""); + if (!sym) { + err(s->c, n->loc, "unknown name"); + return unknown(s->c); + } + if (sym->fn) { + err(s->c, n->loc, "function is not assignable"); + return unknown(s->c); + } + if (!sym->mutable) + err(s->c, n->loc, "cannot assign to immutable let"); + n->cname = sym->cname; + n->sem_type = sym->type; + if (read && !sym->initialized) + err(s->c, n->loc, "use of uninitialized variable"); + return sym->type; + } + if (n) err(s->c, n->loc, "assignment requires a variable"); + return unknown(s->c); +} + +static int compound_operator(const char *op) +{ + return op && strcmp(op, "=") != 0; +} + +static void check_stmt(FeCheckerState *s, FeNode *n) +{ + FeCheck *c = s->c; + FeScope *old; + FeType *a; + FeType *b; + FeSym *sym; + FeNode *x; + int initialized; + if (!n) return; + switch (n->kind) { + case FE_N_BLOCK: + old = s->scope; + s->scope = scope_new(s, old); + for (x = n->children; x; x = x->next) check_stmt(s, x); + s->scope = old; + break; + case FE_N_LET: + case FE_N_CONST: + a = n->a ? node_type(c, n->a) : unknown(c); + b = check_expr(s, n->b); + if (!n->a) a = b; + if (a->kind == FE_TYPE_VOID) + err(c, n->loc, "variable cannot have void type"); + if (n->a && !compatible(a, b, n->b) && b->kind != FE_TYPE_UNKNOWN) + err(c, n->loc, "initializer type mismatch"); + if (b->kind == FE_TYPE_VOID) + err(c, n->loc, "void expression cannot initialize a variable"); + add_symbol(s, s->scope, n->text, a, 0, 0, 1, + local_cname(c, n->text ? n->text : "local"), n); + break; + case FE_N_VAR: + a = n->a ? node_type(c, n->a) : unknown(c); + if (!n->b && !n->a) + err(c, n->loc, "uninitialized var requires an explicit type"); + b = n->b ? check_expr(s, n->b) : unknown(c); + if (!n->a && n->b) a = b; + if (a->kind == FE_TYPE_VOID) + err(c, n->loc, "variable cannot have void type"); + if (n->b && !compatible(a, b, n->b) && b->kind != FE_TYPE_UNKNOWN) + err(c, n->loc, "initializer type mismatch"); + if (b->kind == FE_TYPE_VOID) + err(c, n->loc, "void expression cannot initialize a variable"); + initialized = n->b != 0; + add_symbol(s, s->scope, n->text, a, 0, 1, initialized, + local_cname(c, n->text ? n->text : "local"), n); + break; + case FE_N_ASSIGN: + b = check_expr(s, n->b); + a = check_lvalue(s, n->a, compound_operator(n->text)); + if (!compatible(a, b, n->b) && b->kind != FE_TYPE_UNKNOWN) + err(c, n->loc, "assignment type mismatch"); + sym = n->a && n->a->kind == FE_N_IDENT ? + find_symbol(s->scope, n->a->text) : 0; + if (sym && sym->mutable) sym->initialized = 1; + break; + case FE_N_EXPR_STMT: + check_expr(s, n->a); + break; + case FE_N_IF: + a = check_expr(s, n->a); + if (known(a) && a->kind != FE_TYPE_BOOL) + err(c, n->loc, "if condition must be bool"); + check_stmt(s, n->b); + check_stmt(s, n->c); + break; + case FE_N_WHILE: + a = check_expr(s, n->a); + if (known(a) && a->kind != FE_TYPE_BOOL) + err(c, n->loc, "while condition must be bool"); + check_stmt(s, n->b); + break; + case FE_N_RETURN: + b = n->a ? check_expr(s, n->a) : fe_type_intern(&c->types, "void"); + if (known(b) && b->kind == FE_TYPE_VOID && s->ret->kind != FE_TYPE_VOID) + err(c, n->loc, "void expression returned from value function"); + else if (known(s->ret) && known(b) && !fe_type_equal(s->ret, b) && + b->kind != FE_TYPE_UNKNOWN) + err(c, n->loc, "return type mismatch"); + break; + case FE_N_UNSAFE: + check_stmt(s, n->a); + break; + default: + break; + } +} + +static void check_fn(FeCheck *c, FeNode *fn, FeScope *globals) +{ + FeCheckerState s; + FeScope *old; + FeNode *x; + FeType *t; + s.c = c; + s.globals = globals; + s.scope = scope_new(&s, globals); + s.ret = fn->b ? node_type(c, fn->b) : fe_type_intern(&c->types, "void"); + fn->sem_type = s.ret; + for (x = fn->a ? fn->a->children : 0; x; x = x->next) { + t = node_type(c, x->a); + if (t->kind == FE_TYPE_VOID) + err(c, x->loc, "parameter cannot have void type"); + add_symbol(&s, s.scope, x->text, t, 0, 1, 1, + local_cname(c, x->text ? x->text : "arg"), x); + } + old = s.scope; + if (fn->c) check_stmt(&s, fn->c); + s.scope = old; +} + +int fe_check_program(FeCheck *c) +{ + FeCheckerState s; + FeNode *n; + FeSym *sym; + FeType *t; + FeType *iv; + s.c = c; + s.scope = scope_new(&s, 0); + s.globals = s.scope; + s.ret = fe_type_intern(&c->types, "void"); + for (n = c->ast->root ? c->ast->root->children : 0; n; n = n->next) { + if (n->kind == FE_N_GLOBAL || n->kind == FE_N_CONST) { + t = n->a ? node_type(c, n->a) : unknown(c); + add_symbol(&s, s.globals, n->text, t, 0, + n->kind == FE_N_GLOBAL, n->b != 0, + unit_cname(c, n->text ? n->text : "global"), n); + } + } + for (n = c->ast->root ? c->ast->root->children : 0; n; n = n->next) { + if (n->kind == FE_N_FN) { + t = fe_type_intern(&c->types, ""); + add_symbol(&s, s.globals, n->text, t, n, 0, 1, + unit_cname(c, n->text ? n->text : "fn"), n); + } + } + for (n = c->ast->root ? c->ast->root->children : 0; n; n = n->next) { + if (n->kind == FE_N_GLOBAL || n->kind == FE_N_CONST) { + sym = find_current(s.globals, n->text ? n->text : ""); + if (n->b) { + iv = check_expr(&s, n->b); + if (sym && sym->type->kind == FE_TYPE_UNKNOWN) { + sym->type = iv; + n->sem_type = iv; + } else if (sym && !compatible(sym->type, iv, n->b) && + iv->kind != FE_TYPE_UNKNOWN) + err(c, n->loc, "global initializer type mismatch"); + if (iv->kind == FE_TYPE_VOID) + err(c, n->loc, "void expression cannot initialize a global"); + } + } + } + for (n = c->ast->root ? c->ast->root->children : 0; n; n = n->next) + if (n->kind == FE_N_FN) check_fn(c, n, s.globals); + return c->diags->errors == 0; +} + +FeType *fe_check_expr_type(FeCheck *c, FeNode *n) +{ + FeCheckerState s; + s.c = c; + s.scope = scope_new(&s, 0); + s.globals = s.scope; + s.ret = fe_type_intern(&c->types, "void"); + return check_expr(&s, n); +} diff --git a/fec/src/check.h b/fec/src/check.h new file mode 100644 index 0000000..b30cb3c --- /dev/null +++ b/fec/src/check.h @@ -0,0 +1,19 @@ +#ifndef FE_CHECK_H +#define FE_CHECK_H + +#include "types.h" +#include "diag.h" + +typedef struct FeCheck { + FeAst *ast; + FeTypeCtx types; + FeDiags *diags; + unsigned pointer_bits; + unsigned local_serial; +} FeCheck; + +void fe_check_init(FeCheck *c, FeAst *ast, FeDiags *diags, unsigned pointer_bits); +int fe_check_program(FeCheck *c); +FeType *fe_check_expr_type(FeCheck *c, FeNode *n); + +#endif diff --git a/fec/src/driver.c b/fec/src/driver.c index 5c52c99..ad6d117 100644 --- a/fec/src/driver.c +++ b/fec/src/driver.c @@ -1,4 +1,6 @@ #include "parser.h" +#include "check.h" +#include "emit_c.h" #include #include #include @@ -12,14 +14,18 @@ static char *read_file(const char *name, unsigned long *size) 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]"); } +{ puts("usage: fec [--dump-ast|--emit-c] file.fe [--target=bits16|bits32] [-o output.c]"); } 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; + int i,dump=0,emit=0; const char *file=0,*outname=0; unsigned long n; char *src; FeDiags d; FeAst ast; FeParser p; FeCheck check; FeEmitter emitter; FILE *out; unsigned pointer_bits=32; + (void)emit; if(argc<2){usage();return 2;} - for(i=1;i=argc){fprintf(stderr,"fec: -o needs a path\n");return 2;}outname=argv[++i];} else if(strncmp(argv[i],"-o",2)==0 && argv[i][2]) outname=argv[i]+2; else if(strncmp(argv[i],"--target=bits16",15)==0) pointer_bits=16; else if(strncmp(argv[i],"--target=bits32",15)==0) pointer_bits=32; else if(strncmp(argv[i],"--target=",9)==0 || strncmp(argv[i],"--model=",8)==0 || strcmp(argv[i],"--no-checks")==0 || strcmp(argv[i],"--strip-error-names")==0) { } else if(argv[i][0]!='-') file=argv[i]; else if(strcmp(argv[i],"--help")==0){usage();return 0;} else {fprintf(stderr,"fec: unknown option %s\n",argv[i]);return 2;} } if(!file){fprintf(stderr,"fec: no input file\n");return 2;} src=read_file(file,&n);if(!src)return 2;d.errors=0;d.warnings=0;fe_ast_init(&ast);fe_parser_init(&p,&ast,src,n,file,&d);ast.root=fe_parse_unit(&p); - if(dump) fe_ast_dump(ast.root,0,stdout); + if(dump) { fe_ast_dump(ast.root,0,stdout); fe_ast_destroy(&ast); free(src); return d.errors?1:0; } + fe_check_init(&check,&ast,&d,pointer_bits); if(!fe_check_program(&check)){fe_ast_destroy(&ast);free(src);return 1;} + out=outname?fopen(outname,"w"):stdout; if(!out){fprintf(stderr,"fec: cannot create %s\n",outname);fe_ast_destroy(&ast);free(src);return 2;} + fe_emit_c_init(&emitter,out,&check,pointer_bits);fe_emit_c_program(&emitter);if(outname)fclose(out); fe_ast_destroy(&ast); free(src); return d.errors?1:0; } diff --git a/fec/src/emit_c.c b/fec/src/emit_c.c new file mode 100644 index 0000000..e696aea --- /dev/null +++ b/fec/src/emit_c.c @@ -0,0 +1,280 @@ +#include "emit_c.h" +#include +#include + +static void pad(FeEmitter *e) +{ + int i; + for (i = 0; i < e->indent; ++i) fputs(" ", e->out); +} + +static const char *ctype(FeEmitter *e, FeNode *n) +{ + FeType *t; + if (n && n->sem_type) t = n->sem_type; + else if (n) t = fe_type_from_ast(&e->check->types, n); + else t = fe_type_intern(&e->check->types, "i32"); + return fe_type_c_name(t, e->pointer_bits); +} + +static const char *cname(FeNode *n, const char *fallback) +{ + return n && n->cname ? n->cname : fallback; +} + +static void emit_expr(FeEmitter *e, FeNode *n); +static void emit_stmt(FeEmitter *e, FeNode *n); + +static void emit_expr(FeEmitter *e, FeNode *n) +{ + FeNode *x; + const char *op; + if (!n) { + fputs("0", e->out); + return; + } + switch (n->kind) { + case FE_N_IDENT: + fputs(cname(n, "fe_missing"), e->out); + break; + case FE_N_LITERAL: + if (n->text && strcmp(n->text, "true") == 0) fputs("1", e->out); + else if (n->text && strcmp(n->text, "false") == 0) fputs("0", e->out); + else fputs(n->text ? n->text : "0", e->out); + break; + case FE_N_UNARY: + op = n->text ? n->text : ""; + if (strcmp(op, "not") == 0) fputs("(!", e->out); + else { + fputc('(', e->out); + fputs(op, e->out); + } + emit_expr(e, n->a); + fputc(')', e->out); + break; + case FE_N_BINARY: + op = n->text ? n->text : "+"; + fputc('(', e->out); + emit_expr(e, n->a); + if (strcmp(op, "and") == 0) fputs(" && ", e->out); + else if (strcmp(op, "or") == 0) fputs(" || ", e->out); + else fputs(op, e->out); + emit_expr(e, n->b); + fputc(')', e->out); + break; + case FE_N_TYPE: + if (n->text && strcmp(n->text, "as") == 0) { + fputs("((", e->out); + fputs(ctype(e, n->b), e->out); + fputc(')', e->out); + emit_expr(e, n->a); + fputc(')', e->out); + } else emit_expr(e, n->a); + break; + case FE_N_CALL: + if (n->a) emit_expr(e, n->a); + else fputs(n->text ? n->text : "fe_builtin", e->out); + fputc('(', e->out); + for (x = n->children; x; x = x->next) { + if (x != n->children) fputs(", ", e->out); + emit_expr(e, x); + } + fputc(')', e->out); + break; + case FE_N_MEMBER: + emit_expr(e, n->a); + fputc('.', e->out); + if (n->b) fputs(n->b->text ? n->b->text : "member", e->out); + break; + default: + fputs("0", e->out); + break; + } +} + +static void emit_decl(FeEmitter *e, FeNode *n) +{ + pad(e); + fputs(ctype(e, n), e->out); + fputc(' ', e->out); + fputs(cname(n, "fe_local"), e->out); + fputs(";\n", e->out); +} + +static void emit_block(FeEmitter *e, FeNode *n) +{ + FeNode *x; + if (!n) { + pad(e); + fputs("{\n", e->out); + ++e->indent; + --e->indent; + pad(e); + fputc('}', e->out); + return; + } + pad(e); + fputs("{\n", e->out); + ++e->indent; + /* C89 requires declarations before statements in each actual block. */ + for (x = n->children; x; x = x->next) + if (x->kind == FE_N_LET || x->kind == FE_N_VAR) emit_decl(e, x); + for (x = n->children; x; x = x->next) emit_stmt(e, x); + --e->indent; + pad(e); + fputc('}', e->out); +} + +static void emit_stmt(FeEmitter *e, FeNode *n) +{ + if (!n) return; + switch (n->kind) { + case FE_N_BLOCK: + emit_block(e, n); + fputc('\n', e->out); + break; + case FE_N_LET: + case FE_N_VAR: + if (n->b) { + pad(e); + fputs(cname(n, "fe_local"), e->out); + fputs(" = ", e->out); + emit_expr(e, n->b); + fputs(";\n", e->out); + } + break; + case FE_N_ASSIGN: + pad(e); + emit_expr(e, n->a); + fputc(' ', e->out); + fputs(n->text ? n->text : "=", e->out); + fputs(" ", e->out); + emit_expr(e, n->b); + fputs(";\n", e->out); + break; + case FE_N_EXPR_STMT: + pad(e); + emit_expr(e, n->a); + fputs(";\n", e->out); + break; + case FE_N_RETURN: + pad(e); + fputs("return", e->out); + if (n->a) { + fputc(' ', e->out); + emit_expr(e, n->a); + } + fputs(";\n", e->out); + break; + case FE_N_IF: + pad(e); + fputs("if (", e->out); + emit_expr(e, n->a); + fputs(") ", e->out); + if (n->b && n->b->kind == FE_N_BLOCK) emit_block(e, n->b); + else emit_block(e, 0); + if (n->c) { + fputs(" else ", e->out); + if (n->c->kind == FE_N_IF) emit_stmt(e, n->c); + else emit_block(e, n->c); + } + fputc('\n', e->out); + break; + case FE_N_WHILE: + pad(e); + fputs("while (", e->out); + emit_expr(e, n->a); + fputs(") ", e->out); + if (n->b && n->b->kind == FE_N_BLOCK) emit_block(e, n->b); + else emit_block(e, 0); + fputc('\n', e->out); + break; + default: + break; + } +} + +static void emit_fn(FeEmitter *e, FeNode *fn, int prototype) +{ + FeNode *p; + const char *ret; + ret = fn->sem_type ? fe_type_c_name(fn->sem_type, e->pointer_bits) : + (fn->b ? ctype(e, fn->b) : "void"); + fputs(ret, e->out); + fputc(' ', e->out); + fputs(cname(fn, "fe_fn"), e->out); + fputc('(', e->out); + p = fn->a ? fn->a->children : 0; + if (!p) fputs("void", e->out); + while (p) { + if (p != fn->a->children) fputs(", ", e->out); + fputs(ctype(e, p->a), e->out); + fputc(' ', e->out); + fputs(cname(p, "fe_arg"), e->out); + p = p->next; + } + fputc(')', e->out); + if (prototype) fputs(";\n", e->out); + else { + fputs(" ", e->out); + emit_block(e, fn->c); + fputc('\n', e->out); + } +} + +static void emit_main_wrapper(FeEmitter *e, FeNode *fn) +{ + fputs("int main(void) {\n ", e->out); + if (fn->sem_type && fn->sem_type->kind == FE_TYPE_VOID) { + fputs(cname(fn, "fe_main"), e->out); + fputs("();\n return 0;\n", e->out); + } else { + fputs("return ", e->out); + fputs(cname(fn, "fe_main"), e->out); + fputs("();\n", e->out); + } + fputs("}\n", e->out); +} + +void fe_emit_c_init(FeEmitter *e, FILE *out, FeCheck *check, + unsigned pointer_bits) +{ + e->out = out; + e->check = check; + e->pointer_bits = pointer_bits; + e->indent = 0; +} + +void fe_emit_c_program(FeEmitter *e) +{ + FeNode *n; + FeNode *main_fn = 0; + fputs("/* generated by fec M2 */\n#include \n\n", e->out); + for (n = e->check->ast->root ? e->check->ast->root->children : 0; + n; n = n->next) { + if (n->kind == FE_N_GLOBAL || n->kind == FE_N_CONST) { + fputs(ctype(e, n), e->out); + fputc(' ', e->out); + fputs(cname(n, "fe_global"), e->out); + if (n->b) { + fputs(" = ", e->out); + emit_expr(e, n->b); + } + fputs(";\n", e->out); + } + } + for (n = e->check->ast->root ? e->check->ast->root->children : 0; + n; n = n->next) + if (n->kind == FE_N_FN) { + emit_fn(e, n, 1); + if (n->text && strcmp(n->text, "main") == 0) main_fn = n; + } + fputc('\n', e->out); + for (n = e->check->ast->root ? e->check->ast->root->children : 0; + n; n = n->next) + if (n->kind == FE_N_FN) emit_fn(e, n, 0); + if (main_fn) { + fputc('\n', e->out); + emit_main_wrapper(e, main_fn); + } +} diff --git a/fec/src/emit_c.h b/fec/src/emit_c.h new file mode 100644 index 0000000..0d2aa29 --- /dev/null +++ b/fec/src/emit_c.h @@ -0,0 +1,16 @@ +#ifndef FE_EMIT_C_H +#define FE_EMIT_C_H + +#include "check.h" + +typedef struct FeEmitter { + FILE *out; + FeCheck *check; + unsigned pointer_bits; + int indent; +} FeEmitter; + +void fe_emit_c_init(FeEmitter *e, FILE *out, FeCheck *check, unsigned pointer_bits); +void fe_emit_c_program(FeEmitter *e); + +#endif diff --git a/fec/src/parser.c b/fec/src/parser.c index 4efa04d..434befa 100644 --- a/fec/src/parser.c +++ b/fec/src/parser.c @@ -172,8 +172,8 @@ 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_LET)) { n=toknode(p,FE_N_LET,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 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);n->text=fe_arena_strdup(&p->ast->arena,p->previous.begin,p->previous.length);}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; } diff --git a/fec/src/types.c b/fec/src/types.c new file mode 100644 index 0000000..37a17d0 --- /dev/null +++ b/fec/src/types.c @@ -0,0 +1,50 @@ +#include "types.h" +#include + +void fe_types_init(FeTypeCtx *ctx, FeArena *arena, unsigned pointer_bits) +{ ctx->arena=arena; ctx->types=0; ctx->pointer_bits=pointer_bits; } + +FeType *fe_type_intern(FeTypeCtx *ctx, const char *name) +{ + FeType *t; unsigned i; unsigned bits=0; int uns=0; FeTypeKind kind=FE_TYPE_UNKNOWN; + if (!name) name=""; + for (t=ctx->types;t;t=t->next) if(strcmp(t->name,name)==0) return t; + if(strcmp(name,"void")==0) kind=FE_TYPE_VOID; + else if(strcmp(name,"bool")==0) kind=FE_TYPE_BOOL; + else if(strcmp(name,"i8")==0||strcmp(name,"u8")==0){kind=FE_TYPE_INT;bits=8;uns=name[0]=='u';} + else if(strcmp(name,"i16")==0||strcmp(name,"u16")==0){kind=FE_TYPE_INT;bits=16;uns=name[0]=='u';} + else if(strcmp(name,"i32")==0||strcmp(name,"u32")==0){kind=FE_TYPE_INT;bits=32;uns=name[0]=='u';} + else if(strcmp(name,"usize")==0||strcmp(name,"isize")==0){kind=FE_TYPE_INT;bits=ctx->pointer_bits;uns=name[0]=='u';} + t=(FeType *)fe_arena_alloc(ctx->arena,sizeof(FeType)); if(!t)return 0; + for(i=0;iname)-1 && name[i];i++) t->name[i]=name[i]; + t->name[i]='\0'; + t->kind=kind;t->bits=bits;t->is_unsigned=uns;t->next=ctx->types;ctx->types=t;return t; +} + +FeType *fe_type_from_ast(FeTypeCtx *ctx, const FeNode *node) +{ + if(!node)return fe_type_intern(ctx,""); + if(node->kind!=FE_N_TYPE)return fe_type_intern(ctx,""); + if(node->text && (strcmp(node->text,"?")==0||strcmp(node->text,"!")==0||strcmp(node->text,"^")==0||strcmp(node->text,"&")==0||strcmp(node->text,"*")==0||strcmp(node->text,"far")==0))return fe_type_intern(ctx,""); + if(node->text && strcmp(node->text,"as")==0)return fe_type_from_ast(ctx,node->b); + if(node->text && strcmp(node->text,"fn")==0)return fe_type_intern(ctx,""); + return fe_type_intern(ctx,node->text); +} +int fe_type_equal(const FeType *a,const FeType *b){return a==b || (a&&b&&strcmp(a->name,b->name)==0);} +int fe_type_is_integer(const FeType *t){return t&&t->kind==FE_TYPE_INT;} +const char *fe_type_c_name(const FeType *t,unsigned pointer_bits) +{ + if(!t)return "int"; + if(t->kind==FE_TYPE_VOID)return "void"; + if(t->kind==FE_TYPE_BOOL)return "unsigned char"; + if(t->kind!=FE_TYPE_INT)return "int"; + if(strcmp(t->name,"usize")==0)return pointer_bits==16?"unsigned short":"unsigned long"; + if(strcmp(t->name,"isize")==0)return pointer_bits==16?"short":"long"; + if(strcmp(t->name,"i8")==0)return "signed char"; + if(strcmp(t->name,"u8")==0)return "unsigned char"; + if(strcmp(t->name,"i16")==0)return "short"; + if(strcmp(t->name,"u16")==0)return "unsigned short"; + if(strcmp(t->name,"i32")==0)return "long"; + if(strcmp(t->name,"u32")==0)return "unsigned long"; + return "int"; +} diff --git a/fec/src/types.h b/fec/src/types.h new file mode 100644 index 0000000..290777f --- /dev/null +++ b/fec/src/types.h @@ -0,0 +1,31 @@ +#ifndef FE_TYPES_H +#define FE_TYPES_H + +#include "ast.h" + +typedef enum FeTypeKind { + FE_TYPE_ERROR, FE_TYPE_VOID, FE_TYPE_BOOL, FE_TYPE_INT, FE_TYPE_UNKNOWN +} FeTypeKind; + +struct FeType { + FeTypeKind kind; + char name[16]; + unsigned bits; + int is_unsigned; + FeType *next; +}; + +typedef struct FeTypeCtx { + FeArena *arena; + FeType *types; + unsigned pointer_bits; +} FeTypeCtx; + +void fe_types_init(FeTypeCtx *ctx, FeArena *arena, unsigned pointer_bits); +FeType *fe_type_intern(FeTypeCtx *ctx, const char *name); +FeType *fe_type_from_ast(FeTypeCtx *ctx, const FeNode *node); +int fe_type_equal(const FeType *a, const FeType *b); +int fe_type_is_integer(const FeType *t); +const char *fe_type_c_name(const FeType *t, unsigned pointer_bits); + +#endif diff --git a/fec/test-dos.bat b/fec/test-dos.bat index 36935d8..5427a5e 100644 --- a/fec/test-dos.bat +++ b/fec/test-dos.bat @@ -6,6 +6,10 @@ 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 +if "%WATCOM%"=="" set WATCOM=C:\DEVEL\WATCOMC +if not exist %WATCOM%\BINW\WCL.EXE goto test_fail +if not exist %WATCOM%\BINW\WCL386.EXE goto test_fail +set PATH=%WATCOM%\BINW;%WATCOM%\BINP;%PATH% fec.exe --dump-ast TESTS\PASS\BASIC.FE > nul if errorlevel 1 goto test_fail @@ -40,6 +44,55 @@ if not errorlevel 1 goto test_fail fec.exe --dump-ast TESTS\FAIL\LOGICA.FE > nul if not errorlevel 1 goto test_fail +if exist TESTS\M2\HELLO.C del TESTS\M2\HELLO.C +if exist TESTS\M2\HELLO.EXE del TESTS\M2\HELLO.EXE +if exist TESTS\M2\SCOPES.C del TESTS\M2\SCOPES.C +if exist TESTS\M2\SCOPES.EXE del TESTS\M2\SCOPES.EXE +if exist TESTS\M2\CAST16.C del TESTS\M2\CAST16.C +if exist TESTS\M2\CAST16.EXE del TESTS\M2\CAST16.EXE + +rem M2 bits32 path: Open Watcom 32-bit compiler and DOS extender executable. +fec.exe --target=bits32 --emit-c TESTS\M2\HELLO.FE -o TESTS\M2\HELLO.C > nul +if errorlevel 1 goto test_fail +wcl386 -q -za -bt=dos -fe=TESTS\M2\HELLO.EXE TESTS\M2\HELLO.C +if errorlevel 1 goto test_fail +TESTS\M2\HELLO.EXE +if errorlevel 1 goto test_fail + +fec.exe --target=bits32 --emit-c TESTS\M2\SCOPES.FE -o TESTS\M2\SCOPES.C > nul +if errorlevel 1 goto test_fail +wcl386 -q -za -bt=dos -fe=TESTS\M2\SCOPES.EXE TESTS\M2\SCOPES.C +if errorlevel 1 goto test_fail +TESTS\M2\SCOPES.EXE +if errorlevel 1 goto test_fail + +rem M2 bits16 regression path remains on compiler A (wcl). +fec.exe --target=bits16 --emit-c TESTS\M2\CAST-W.FE -o TESTS\M2\CAST16.C > nul +if errorlevel 1 goto test_fail +wcl -q -za -bt=dos -fe=TESTS\M2\CAST16.EXE TESTS\M2\CAST16.C +if errorlevel 1 goto test_fail +TESTS\M2\CAST16.EXE +if errorlevel 1 goto test_fail + +fec.exe --target=bits32 --emit-c TESTS\M2\BAD-CO.FE -o TESTS\M2\BAD-CO.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M2\BAD-CA.FE -o TESTS\M2\BAD-CA.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M2\BAD-AS.FE -o TESTS\M2\BAD-AS.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M2\BAD-UN.FE -o TESTS\M2\BAD-UN.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M2\BAD-AR.FE -o TESTS\M2\BAD-AR.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M2\BAD-TY.FE -o TESTS\M2\BAD-TY.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M2\BAD-RE.FE -o TESTS\M2\BAD-RE.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M2\BAD-UI.FE -o TESTS\M2\BAD-UI.C > nul +if not errorlevel 1 goto test_fail +fec.exe --target=bits32 --emit-c TESTS\M2\BAD-VO.FE -o TESTS\M2\BAD-VO.C > nul +if not errorlevel 1 goto test_fail + echo OK>TEST.OK cd C:\FEC goto test_done diff --git a/fec/tests/m2/bad-arity.fe b/fec/tests/m2/bad-arity.fe new file mode 100644 index 0000000..2ba4b2a --- /dev/null +++ b/fec/tests/m2/bad-arity.fe @@ -0,0 +1,9 @@ +unit bad_arity; + +fn add(a: i32, b: i32) -> i32 { + return a + b; +} + +fn main() -> i32 { + return add(1); +} diff --git a/fec/tests/m2/bad-assign.fe b/fec/tests/m2/bad-assign.fe new file mode 100644 index 0000000..fac907a --- /dev/null +++ b/fec/tests/m2/bad-assign.fe @@ -0,0 +1,7 @@ +unit bad_assign; + +fn main() -> i32 { + let value: i32 = 1; + value = 2; + return value; +} diff --git a/fec/tests/m2/bad-cast.fe b/fec/tests/m2/bad-cast.fe new file mode 100644 index 0000000..0eafcfc --- /dev/null +++ b/fec/tests/m2/bad-cast.fe @@ -0,0 +1,6 @@ +unit bad_cast; + +fn main() -> i32 { + let x: i32 = true as i32; + return x; +} diff --git a/fec/tests/m2/bad-condition.fe b/fec/tests/m2/bad-condition.fe new file mode 100644 index 0000000..2e706fa --- /dev/null +++ b/fec/tests/m2/bad-condition.fe @@ -0,0 +1,6 @@ +unit bad_condition; + +fn main() -> i32 { + if 1 { return 0; } + return 1; +} diff --git a/fec/tests/m2/bad-return.fe b/fec/tests/m2/bad-return.fe new file mode 100644 index 0000000..914cd99 --- /dev/null +++ b/fec/tests/m2/bad-return.fe @@ -0,0 +1,5 @@ +unit bad_return; + +fn main() -> i32 { + return true; +} diff --git a/fec/tests/m2/bad-types.fe b/fec/tests/m2/bad-types.fe new file mode 100644 index 0000000..1f277fe --- /dev/null +++ b/fec/tests/m2/bad-types.fe @@ -0,0 +1,9 @@ +unit bad_types; + +fn add(a: i32, b: i32) -> i32 { + return a + b; +} + +fn main() -> i32 { + return add(true, 1); +} diff --git a/fec/tests/m2/bad-uninit.fe b/fec/tests/m2/bad-uninit.fe new file mode 100644 index 0000000..51f6092 --- /dev/null +++ b/fec/tests/m2/bad-uninit.fe @@ -0,0 +1,6 @@ +unit bad_uninit; + +fn main() -> i32 { + var value: i32; + return value; +} diff --git a/fec/tests/m2/bad-unknown.fe b/fec/tests/m2/bad-unknown.fe new file mode 100644 index 0000000..51d1b01 --- /dev/null +++ b/fec/tests/m2/bad-unknown.fe @@ -0,0 +1,5 @@ +unit bad_unknown; + +fn main() -> i32 { + return missing_name; +} diff --git a/fec/tests/m2/bad-void.fe b/fec/tests/m2/bad-void.fe new file mode 100644 index 0000000..bf3ce1f --- /dev/null +++ b/fec/tests/m2/bad-void.fe @@ -0,0 +1,10 @@ +unit bad_void; + +fn noop() { + return; +} + +fn main() -> i32 { + let value: i32 = noop(); + return value; +} diff --git a/fec/tests/m2/cast-while.fe b/fec/tests/m2/cast-while.fe new file mode 100644 index 0000000..c54b114 --- /dev/null +++ b/fec/tests/m2/cast-while.fe @@ -0,0 +1,11 @@ +unit cast_while; + +pub fn main() -> i32 { + var x: i16 = 0; + while x < 3 { + x += 1; + } + let y: i32 = x as i32; + if y == 3 { return 0; } + return 1; +} diff --git a/fec/tests/m2/hello.fe b/fec/tests/m2/hello.fe new file mode 100644 index 0000000..aec68c7 --- /dev/null +++ b/fec/tests/m2/hello.fe @@ -0,0 +1,23 @@ +unit hello; + +fn add(a: i32, b: i32) -> i32 { + return a + b; +} + +fn is_answer(value: i32) -> bool { + return value == 42; +} + +fn touch() { + return; +} + +pub fn main() -> i32 { + touch(); + let value: i32 = add(20, 22); + if is_answer(value) { + return 0; + } else { + return 1; + } +} diff --git a/fec/tests/m2/scopes.fe b/fec/tests/m2/scopes.fe new file mode 100644 index 0000000..b0d6168 --- /dev/null +++ b/fec/tests/m2/scopes.fe @@ -0,0 +1,15 @@ +unit scopes; + +fn register(switch: i32) -> i32 { + let auto: i32 = switch; + if true { + let auto: i32 = auto + 1; + if auto == 2 { return 0; } + } + if auto == 1 { return 0; } + return 1; +} + +pub fn main() -> i32 { + return register(1); +} diff --git a/fec/tests/run-tests.sh b/fec/tests/run-tests.sh index 6b4eef7..d7b4d75 100644 --- a/fec/tests/run-tests.sh +++ b/fec/tests/run-tests.sh @@ -18,3 +18,25 @@ for f in "$root"/tests/fail/*.fe; do ok=$((ok+1)) done echo "M1 tests: $ok cases passed" + +m2tmp=$(mktemp -d) +trap 'rm -rf "$m2tmp"' EXIT HUP INT TERM +"$root"/fec --emit-c "$root"/tests/m2/hello.fe -o "$m2tmp/hello.c" +${CC:-cc} -std=c89 -pedantic "$m2tmp/hello.c" -o "$m2tmp/hello" +"$m2tmp/hello" +"$root"/fec --target=bits16 --emit-c "$root"/tests/m2/hello.fe -o "$m2tmp/hello16.c" +${CC:-cc} -std=c89 -pedantic "$m2tmp/hello16.c" -o "$m2tmp/hello16" +"$m2tmp/hello16" +"$root"/fec --emit-c "$root"/tests/m2/cast-while.fe -o "$m2tmp/cast.c" +${CC:-cc} -std=c89 -pedantic "$m2tmp/cast.c" -o "$m2tmp/cast" +"$m2tmp/cast" +"$root"/fec --emit-c "$root"/tests/m2/scopes.fe -o "$m2tmp/scopes.c" +${CC:-cc} -std=c89 -pedantic "$m2tmp/scopes.c" -o "$m2tmp/scopes" +"$m2tmp/scopes" +for f in bad-condition bad-cast bad-assign bad-unknown bad-arity bad-types bad-return bad-uninit bad-void; do + if "$root"/fec --emit-c "$root"/tests/m2/$f.fe -o "$m2tmp/$f.c" >/dev/null 2>/dev/null; then + echo "FAIL (accepted M2 semantic error): $f.fe" + exit 1 + fi +done +echo "M2 tests: integer control-flow smoke passed" diff --git a/fec/vm-m1.bat b/fec/vm-m1.bat index 241d530..af2b722 100644 --- a/fec/vm-m1.bat +++ b/fec/vm-m1.bat @@ -6,7 +6,9 @@ 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 not exist C:\FEC\TESTS\M2 md C:\FEC\TESTS\M2 if exist C:\FEC\VM.FAIL del C:\FEC\VM.FAIL +if exist C:\FEC\STAGE.FAIL del C:\FEC\STAGE.FAIL copy D:\FEC\BUILD-~1.BAT C:\FEC\BUILD.BAT > nul if errorlevel 1 goto stage_fail @@ -35,6 +37,18 @@ 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\SRC\TYPES.C C:\FEC\SRC\TYPES.C > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\SRC\TYPES.H C:\FEC\SRC\TYPES.H > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\SRC\CHECK.C C:\FEC\SRC\CHECK.C > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\SRC\CHECK.H C:\FEC\SRC\CHECK.H > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\SRC\EMIT_C.C C:\FEC\SRC\EMIT_C.C > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\SRC\EMIT_C.H C:\FEC\SRC\EMIT_C.H > 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 @@ -67,6 +81,30 @@ 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 +copy D:\FEC\TESTS\M2\HELLO.FE C:\FEC\TESTS\M2\HELLO.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M2\SCOPES.FE C:\FEC\TESTS\M2\SCOPES.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M2\BAD-CO~1.FE C:\FEC\TESTS\M2\BAD-CO.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M2\BAD-CAST.FE C:\FEC\TESTS\M2\BAD-CA.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M2\BAD-ASSI~1.FE C:\FEC\TESTS\M2\BAD-AS.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M2\BAD-UN~1.FE C:\FEC\TESTS\M2\BAD-UN.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M2\BAD-UN~2.FE C:\FEC\TESTS\M2\BAD-UI.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M2\BAD-AR~1.FE C:\FEC\TESTS\M2\BAD-AR.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M2\BAD-TY~1.FE C:\FEC\TESTS\M2\BAD-TY.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M2\BAD-RE~1.FE C:\FEC\TESTS\M2\BAD-RE.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M2\BAD-VOID.FE C:\FEC\TESTS\M2\BAD-VO.FE > nul +if errorlevel 1 goto stage_fail +copy D:\FEC\TESTS\M2\CAST-W~1.FE C:\FEC\TESTS\M2\CAST-W.FE > nul +if errorlevel 1 goto stage_fail call C:\FEC\TEST-DOS.BAT if exist C:\FEC\TEST.OK goto vm_success