Skip to content

Commit

Permalink
User-friendly error message for missing main function (#509)
Browse files Browse the repository at this point in the history
Co-authored-by: Akuli <[email protected]>
  • Loading branch information
littlewhitecloud and Akuli authored Aug 21, 2024
1 parent 6171948 commit 7c2f683
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 0 deletions.
24 changes: 24 additions & 0 deletions self_hosted/main.jou
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ def parse_args(argc: int, argv: byte**) -> CommandLineArgs:

return result

def find_file(files: FileState*, nfiles: int, path: byte*) -> FileState*:
for i = 0; i < nfiles; i++:
if strcmp(files[i].ast.path, path) == 0:
return &files[i]
return NULL

# C:\Users\myname\.foo-bar.jou --> "_foo_bar"
# Result never contains "-", so you can add "-" separated suffixes without conflicts.
Expand Down Expand Up @@ -117,6 +122,14 @@ def get_sane_filename(path: byte*) -> byte[50]:
name[i] = '_'
return name


def check_main_function(ast: AstFile*) -> bool:
for i = 0; i < ast->body.nstatements; i++:
s = &ast->body.statements[i]
if s->kind == AstStatementKind::Function and strcmp(s->function.signature.name, "main") == 0:
return True
return False

def check_ast_and_import_conflicts(ast: AstFile*, symbol: ExportSymbol*) -> None:
for i = 0; i < ast->body.nstatements; i++:
ts = &ast->body.statements[i]
Expand Down Expand Up @@ -210,6 +223,10 @@ class Compiler:

evaluate_compile_time_if_statements_in_body(&ast.body)

if item.is_imported and check_main_function(&ast):
assert item.import_location.path != NULL
fail(item.import_location, "imported file should not have `main` function")

self->files = realloc(self->files, sizeof self->files[0] * (self->nfiles + 1))
self->files[self->nfiles++] = FileState{ast = ast}

Expand Down Expand Up @@ -467,6 +484,13 @@ def main(argc: int, argv: byte**) -> int:
compiler.process_imports_and_exports()
compiler.typecheck_stage3_all_files()

mainfile = find_file(compiler.files, compiler.nfiles, args.main_path)
assert mainfile != NULL

if not check_main_function(&mainfile->ast):
l = Location{path=mainfile->ast.path, lineno=0}
fail(l, "missing `main` function to execute the program")

object_files = compiler.create_object_files()
executable = compiler.link(object_files)
for i = 0; object_files[i] != NULL; i++:
Expand Down
29 changes: 29 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,16 @@ static FILE *open_the_file(const char *path, const Location *import_location)
return f;
}

static bool defines_main(const AstFile *ast)
{
for (int i = 0; i < ast->body.nstatements; i++) {
const AstStatement *s = &ast->body.statements[i];
if (s->kind == AST_STMT_FUNCTION && !strcmp(s->data.function.signature.name, "main"))
return true;
}
return false;
}

static void parse_file(struct CompileState *compst, const char *filename, const Location *import_location)
{
if (find_file(compst, filename))
Expand Down Expand Up @@ -224,6 +234,16 @@ static void parse_file(struct CompileState *compst, const char *filename, const
if(command_line_args.verbosity >= 2)
print_ast(&fs.ast);

// If it's not the file passed on command line, it shouldn't define main()
if (strcmp(filename, command_line_args.infile) && defines_main(&fs.ast)) {
/*
Set error location to import, so user immediately knows which file
imports something that defines main().
*/
assert(import_location);
fail(*import_location, "imported file should not have `main` function");
}

for (const AstImport *imp = fs.ast.imports.ptr; imp < End(fs.ast.imports); imp++) {
Append(&compst->parse_queue, (struct ParseQueueItem){
.filename = imp->resolved_path,
Expand Down Expand Up @@ -492,6 +512,15 @@ int main(int argc, char **argv)
for (struct FileState *fs = compst.files.ptr; fs < End(compst.files); fs++)
objpaths[fs - compst.files.ptr] = compile_ast_to_object_file(fs);

/*
Check for missing main() as late as possible, so that other errors come first.
This way Jou users can work on other functions before main() function is written.
*/
struct FileState *mainfile = find_file(&compst, command_line_args.infile);
assert(mainfile);
if (!defines_main(&mainfile->ast))
fail((Location){.filename=mainfile->path, .lineno=0}, "missing `main` function to execute the program");

for (struct FileState *fs = compst.files.ptr; fs < End(compst.files); fs++) {
free_ast(&fs->ast);
free(fs->path);
Expand Down
1 change: 1 addition & 0 deletions tests/other_errors/import_file_with_main.jou
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "../../examples/hello.jou" # Error: imported file should not have `main` function
3 changes: 3 additions & 0 deletions tests/other_errors/missing_main_function.jou
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Output: compiler error in file "tests/other_errors/missing_main_function.jou": missing `main` function to execute the program
def test() -> None:
_ = "lol no main func"

0 comments on commit 7c2f683

Please sign in to comment.