refactor: keep the front end, drop everything downstream of it

The milestone structure had stopped describing the compiler and started
shaping it: m7.c, check_m7.c, tests/m2..m9, and a checker and emitter that had
each grown past 2,500 lines because there was nowhere else to put anything.
Restart from the pipeline instead.

What is left is the front end -- lexer, parser, types, ownership, semantic
analysis -- and the fixtures that describe it. The C backend, the DOSBox-X
runner, the milestone registry and the batch build are removed. The driver now
stops after semantic analysis; a code generator attaches where emit_c did.

Fixtures move from milestone directories to what they check:

    parse/     grammar            own/       ownership and borrowing
    types/     type rules         optional/  optionals and error unions
    format/    formatting, try    units/     units and visibility
    generic/   generics           pending-backend/

pending-backend/ holds the three fixtures that can only be checked by running
a program -- that the bounds check traps, that --no-checks removes it, and that
drops and defers actually fire, verified through a fake allocator. Those are
not front-end tests and are not pretending to be; they come back first when
there is a code generator.

tests/run.py replaces the DOSBox-X harness. It builds the front end with the
pinned Watcom's Windows-hosted driver and runs every fixture in about two
seconds, and it does something the old runner structurally could not: it reads
the `// ERROR:line:text` marker each fixture carries and checks the diagnostic
against it. Those markers have been in the tree all along, unverified, because
DOS could not redirect the compiler's stderr and only the exit code was ever
compared.

133/188 pass. The 55 failures are not regressions -- they are what was already
true and invisible:

- units (27) and generic (23): `import`, `comptime` and generic declarations
  parse and are then dropped on the floor. No pass looks at them. The old
  registry did not list these fixtures at all, so nothing said so.
- five in own/, optional/ and generic/: a marker disagrees with the diagnostic
  about the line or the wording. Each is either a wrong marker or a wrong
  diagnostic and has to be read individually.

Everything removed is in git history.
This commit is contained in:
2026-08-17 03:33:23 +09:00
parent 2696dd2abc
commit fb65152901
221 changed files with 149 additions and 5834 deletions
+3
View File
@@ -25,3 +25,6 @@ __pycache__/
node_modules/
.npm/
.cache/
# host build output of the front end
.build/
-49
View File
@@ -1,49 +0,0 @@
@echo off
rem Open Watcom C89 build. The runner's generated RUN.BAT calls 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 *.obj del *.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 -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=m7.obj src\m7.c
if errorlevel 1 goto build_fail
wcl -q -za -wx -bt=dos -ml -k32768 -c -fo=own.obj src\own.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
wcl -q -za -wx -bt=dos -ml -k32768 -c -fo=lower.obj src\lower.c
if errorlevel 1 goto build_fail
rem Use an unambiguous short object name for the emitter 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
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
cd C:\FEC
goto build_done
:build_fail
echo FAIL>BUILD.FAIL
:build_done
+6 -19
View File
@@ -1,6 +1,5 @@
#include "parser.h"
#include "check.h"
#include "emit_c.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -37,25 +36,20 @@ static void dump_tokens(const char *src, unsigned long n, const char *file,
int main(int argc, char **argv)
{
int i,dump=0,dump_tok=0,check_only=0,emit=0,no_checks=0;
const char *file=0,*outname=0;
int i,dump=0,dump_tok=0,check_only=0,no_checks=0;
const char *file=0;
unsigned long n;
char *src;
FeDiags d;
FeAst ast;
FeParser p;
FeCheck check;
FeEmitter emitter;
FILE *out;
unsigned pointer_bits=32;
if(argc<2){usage();return 2;}
for(i=1;i<argc;i++) {
if(strcmp(argv[i],"--dump-ast")==0) dump=1;
else if(strcmp(argv[i],"--dump-tokens")==0) dump_tok=1;
else if(strcmp(argv[i],"--check")==0) check_only=1;
else if(strcmp(argv[i],"--emit-c")==0) emit=1;
else if(strcmp(argv[i],"-o")==0){if(i+1>=argc){fprintf(fe_diag_stream(),"fec: -o needs a path\n");return 2;}outname=argv[++i];}
else if(strncmp(argv[i],"-o",2)==0 && argv[i][2]) outname=argv[i]+2;
else if(strncmp(argv[i],"--target=bits16",15)==0) pointer_bits=16;
else if(strncmp(argv[i],"--target=bits32",15)==0) pointer_bits=32;
else if(strcmp(argv[i],"--no-checks")==0) no_checks=1;
@@ -64,7 +58,7 @@ int main(int argc, char **argv)
else if(strcmp(argv[i],"--help")==0){usage();return 0;}
else {fprintf(fe_diag_stream(),"fec: unknown option %s\n",argv[i]);return 2;}
}
if((dump?1:0)+(dump_tok?1:0)+(check_only?1:0)+(emit?1:0)>1){
if((dump?1:0)+(dump_tok?1:0)+(check_only?1:0)>1){
fprintf(fe_diag_stream(),"fec: choose only one output mode\n");
return 2;
}
@@ -92,16 +86,9 @@ int main(int argc, char **argv)
free(src);
return 1;
}
if(check_only){
fe_ast_destroy(&ast);
free(src);
return 0;
}
out=outname?fopen(outname,"w"):stdout;
if(!out){fprintf(fe_diag_stream(),"fec: cannot create %s\n",outname);fe_ast_destroy(&ast);free(src);return 2;}
fe_emit_c_init(&emitter,out,&check,pointer_bits,no_checks);
fe_emit_c_program(&emitter);
if(outname)fclose(out);
/* Semantic analysis is the last pass there is. A code generator attaches
here; until then --check and the default path are the same thing. */
(void)check_only;
fe_ast_destroy(&ast);
free(src);
return d.errors?1:0;
-2428
View File
File diff suppressed because it is too large Load Diff
-28
View File
@@ -1,28 +0,0 @@
#ifndef FE_EMIT_C_H
#define FE_EMIT_C_H
#include "check.h"
#include "own.h"
typedef struct FeEmitter {
FILE *out;
FeCheck *check;
unsigned pointer_bits;
int indent;
int no_checks;
unsigned temp_serial;
FeNode *fallthrough_block;
FeNode *block_stack[32];
unsigned block_seen[32];
unsigned block_depth;
unsigned loop_floor[16];
unsigned loop_depth;
FeType *current_ret;
FeNode *current_fn;
} FeEmitter;
void fe_emit_c_init(FeEmitter *e, FILE *out, FeCheck *check,
unsigned pointer_bits, int no_checks);
void fe_emit_c_program(FeEmitter *e);
#endif
-227
View File
@@ -1,227 +0,0 @@
#include "lower.h"
#include <string.h>
static int lower_grow_scopes(FeLowerPlan *plan)
{
FeLowerScope *items;
unsigned capacity;
if (plan->scope_count < plan->scope_capacity) return 1;
capacity = plan->scope_capacity ? plan->scope_capacity * 2U : 8U;
items = (FeLowerScope *)fe_arena_alloc(plan->arena,
capacity * sizeof(FeLowerScope));
if (!items) return 0;
if (plan->scopes)
memcpy(items, plan->scopes,
plan->scope_count * sizeof(FeLowerScope));
plan->scopes = items;
plan->scope_capacity = capacity;
return 1;
}
static int lower_grow_cleanups(FeLowerPlan *plan)
{
FeLowerCleanup *items;
unsigned capacity;
if (plan->cleanup_count < plan->cleanup_capacity) return 1;
capacity = plan->cleanup_capacity ? plan->cleanup_capacity * 2U : 16U;
items = (FeLowerCleanup *)fe_arena_alloc(plan->arena,
capacity * sizeof(FeLowerCleanup));
if (!items) return 0;
if (plan->cleanups)
memcpy(items, plan->cleanups,
plan->cleanup_count * sizeof(FeLowerCleanup));
plan->cleanups = items;
plan->cleanup_capacity = capacity;
return 1;
}
static unsigned lower_add_scope(FeLowerPlan *plan, FeNode *block,
unsigned parent)
{
FeLowerScope *scope;
unsigned index;
if (!lower_grow_scopes(plan)) return FE_LOWER_NO_SCOPE;
index = plan->scope_count++;
scope = &plan->scopes[index];
scope->block = block;
scope->parent = parent;
scope->ordinal = plan->next_ordinal++;
return index;
}
static int lower_add_cleanup(FeLowerPlan *plan, unsigned scope,
FeLowerCleanupKind kind, FeNode *node,
FeNode *decl, FeType *type)
{
FeLowerCleanup *cleanup;
if (!lower_grow_cleanups(plan)) return 0;
cleanup = &plan->cleanups[plan->cleanup_count++];
cleanup->kind = kind;
cleanup->scope = scope;
cleanup->ordinal = plan->next_ordinal++;
cleanup->node = node;
cleanup->decl = decl;
cleanup->type = type;
return 1;
}
int fe_lower_type_needs_drop(const FeType *type)
{
unsigned i;
unsigned j;
if (!type) return 0;
if (type->kind == FE_TYPE_OWNED) return 1;
if (type->kind == FE_TYPE_OPTIONAL)
return fe_lower_type_needs_drop(type->elem);
if (type->kind == FE_TYPE_ERROR_UNION)
return fe_lower_type_needs_drop(type->error_value);
if (type->kind == FE_TYPE_ARRAY)
return fe_lower_type_needs_drop(type->elem);
if (type->kind == FE_TYPE_STRUCT) {
if (type->has_drop) return 1;
for (i = 0; i < type->field_count; ++i)
if (fe_lower_type_needs_drop(type->fields[i].type)) return 1;
return 0;
}
if (type->kind == FE_TYPE_ENUM) {
for (i = 0; i < type->variant_count; ++i)
for (j = 0; j < type->variants[i].field_count; ++j)
if (fe_lower_type_needs_drop(type->variants[i].fields[j].type))
return 1;
}
return 0;
}
static int lower_build_node(FeLowerPlan *plan, FeNode *node,
unsigned scope);
static int lower_build_list(FeLowerPlan *plan, FeNode *node,
unsigned scope)
{
while (node) {
if (!lower_build_node(plan, node, scope)) return 0;
node = node->next;
}
return 1;
}
static int lower_build_block(FeLowerPlan *plan, FeNode *block,
unsigned parent)
{
unsigned scope;
if (!block || block->kind != FE_N_BLOCK) return 1;
scope = lower_add_scope(plan, block, parent);
if (scope == FE_LOWER_NO_SCOPE) return 0;
return lower_build_list(plan, block->children, scope);
}
static int lower_build_node(FeLowerPlan *plan, FeNode *node,
unsigned scope)
{
FeNode *child;
FeType *type;
if (!node) return 1;
if (node->kind == FE_N_BLOCK)
return lower_build_block(plan, node, scope);
if (node->kind == FE_N_DEFER) {
if (!lower_add_cleanup(plan, scope, FE_LOWER_CLEANUP_DEFER,
node->a, node, 0))
return 0;
return lower_build_node(plan, node->a, scope);
}
if (node->kind == FE_N_LET || node->kind == FE_N_VAR ||
node->kind == FE_N_CONST) {
type = node->sem_type;
if (fe_lower_type_needs_drop(type))
if (!lower_add_cleanup(plan, scope, FE_LOWER_CLEANUP_DROP,
node, node, type))
return 0;
}
if (!lower_build_node(plan, node->a, scope)) return 0;
if (!lower_build_node(plan, node->b, scope)) return 0;
if (!lower_build_node(plan, node->c, scope)) return 0;
for (child = node->children; child; child = child->next)
if (!lower_build_node(plan, child, scope)) return 0;
return 1;
}
void fe_lower_plan_init(FeLowerPlan *plan, FeArena *arena)
{
if (!plan) return;
plan->arena = arena;
plan->fn = 0;
plan->scopes = 0;
plan->scope_count = 0;
plan->scope_capacity = 0;
plan->cleanups = 0;
plan->cleanup_count = 0;
plan->cleanup_capacity = 0;
plan->next_ordinal = 0;
}
int fe_lower_plan_build(FeLowerPlan *plan, FeNode *fn)
{
if (!plan || !plan->arena || !fn || fn->kind != FE_N_FN) return 0;
plan->fn = fn;
plan->scopes = 0;
plan->scope_count = 0;
plan->scope_capacity = 0;
plan->cleanups = 0;
plan->cleanup_count = 0;
plan->cleanup_capacity = 0;
plan->next_ordinal = 0;
if (!fn->c) return 1;
return lower_build_block(plan, fn->c, FE_LOWER_NO_SCOPE);
}
unsigned fe_lower_scope_for_block(const FeLowerPlan *plan,
const FeNode *block)
{
unsigned i;
if (!plan || !block) return FE_LOWER_NO_SCOPE;
for (i = 0; i < plan->scope_count; ++i)
if (plan->scopes[i].block == block) return i;
return FE_LOWER_NO_SCOPE;
}
unsigned fe_lower_collect_cleanups(const FeLowerPlan *plan,
const FeNode *from_block,
const FeNode *stop_block,
const FeLowerCleanup **out,
unsigned out_capacity)
{
unsigned scope;
unsigned stop;
unsigned i;
unsigned count;
if (!plan || !from_block) return 0;
scope = fe_lower_scope_for_block(plan, from_block);
stop = stop_block ? fe_lower_scope_for_block(plan, stop_block) :
FE_LOWER_NO_SCOPE;
count = 0;
while (scope != FE_LOWER_NO_SCOPE && scope != stop) {
for (i = plan->cleanup_count; i > 0; --i) {
if (plan->cleanups[i - 1U].scope != scope) continue;
if (out && count < out_capacity)
out[count] = &plan->cleanups[i - 1U];
++count;
}
scope = plan->scopes[scope].parent;
}
return count;
}
int fe_lower_exit_runs_cleanup(FeLowerExitKind kind)
{
return kind == FE_LOWER_EXIT_FALLTHROUGH ||
kind == FE_LOWER_EXIT_RETURN ||
kind == FE_LOWER_EXIT_ERROR_RETURN ||
kind == FE_LOWER_EXIT_BREAK ||
kind == FE_LOWER_EXIT_CONTINUE;
}
int fe_lower_exit_leaves_function(FeLowerExitKind kind)
{
return kind == FE_LOWER_EXIT_RETURN ||
kind == FE_LOWER_EXIT_ERROR_RETURN;
}
-61
View File
@@ -1,61 +0,0 @@
#ifndef FE_LOWER_H
#define FE_LOWER_H
#include "types.h"
#define FE_LOWER_NO_SCOPE ((unsigned)~0U)
typedef enum FeLowerExitKind {
FE_LOWER_EXIT_FALLTHROUGH = 0,
FE_LOWER_EXIT_RETURN,
FE_LOWER_EXIT_ERROR_RETURN,
FE_LOWER_EXIT_BREAK,
FE_LOWER_EXIT_CONTINUE
} FeLowerExitKind;
typedef enum FeLowerCleanupKind {
FE_LOWER_CLEANUP_DROP = 0,
FE_LOWER_CLEANUP_DEFER
} FeLowerCleanupKind;
typedef struct FeLowerScope {
FeNode *block;
unsigned parent;
unsigned ordinal;
} FeLowerScope;
typedef struct FeLowerCleanup {
FeLowerCleanupKind kind;
unsigned scope;
unsigned ordinal;
FeNode *node;
FeNode *decl;
FeType *type;
} FeLowerCleanup;
typedef struct FeLowerPlan {
FeArena *arena;
FeNode *fn;
FeLowerScope *scopes;
unsigned scope_count;
unsigned scope_capacity;
FeLowerCleanup *cleanups;
unsigned cleanup_count;
unsigned cleanup_capacity;
unsigned next_ordinal;
} FeLowerPlan;
void fe_lower_plan_init(FeLowerPlan *plan, FeArena *arena);
int fe_lower_plan_build(FeLowerPlan *plan, FeNode *fn);
unsigned fe_lower_scope_for_block(const FeLowerPlan *plan,
const FeNode *block);
unsigned fe_lower_collect_cleanups(const FeLowerPlan *plan,
const FeNode *from_block,
const FeNode *stop_block,
const FeLowerCleanup **out,
unsigned out_capacity);
int fe_lower_type_needs_drop(const FeType *type);
int fe_lower_exit_runs_cleanup(FeLowerExitKind kind);
int fe_lower_exit_leaves_function(FeLowerExitKind kind);
#endif

Some files were not shown because too many files have changed in this diff Show More