From 2cb971a48364a9d7823b71231aa1ee1b1936149d Mon Sep 17 00:00:00 2001 From: dimgigov Date: Mon, 8 Jun 2026 19:45:05 +0300 Subject: [PATCH] fix(selfhost/parser): skip newlines inside expressions The selfhost parser did not skip tkNewLine tokens inside expression parsing, causing multi-line expressions (e.g. if conditions spanning lines with ||) to be truncated. When parserParseBinaryPrec hit a newline it treated it as an invalid primary, returning a malformed literal and leaving p.pos at the newline. parserParseBlock then failed to find '{' and its depth-counter fallback consumed far too many tokens, causing subsequent function bodies to be parsed into the wrong AST nodes. Fix: add newline skipping in parserParsePrimary, parserParseUnary, and parserParseBinaryPrec, matching the bootstrap parser's skipNewlines() calls in parseAnd/parseOr. Also complete the variant cap raise from 8->9 in src/sema.bux (was already done in ast.bux, parser.bux, hir_lower.bux). --- rt/runtime.c | 88 +++++++++++++++++++++++++++++++---------------- src/ast.bux | 1 + src/hir_lower.bux | 1 + src/parser.bux | 12 ++++++- src/sema.bux | 3 +- 5 files changed, 74 insertions(+), 31 deletions(-) diff --git a/rt/runtime.c b/rt/runtime.c index 7b897c4..1257294 100644 --- a/rt/runtime.c +++ b/rt/runtime.c @@ -647,45 +647,75 @@ unsigned int bux_hash_string(const char* s) { #include #include -/* bux_list_dir: returns array of file paths in dir matching ext suffix. +/* bux_list_dir: returns array of file paths in dir (recursively) matching ext suffix. * Result is malloc'd array of malloc'd strings. Caller must free. * Sets *out_count to number of files found. */ -char** bux_list_dir(const char* dir, const char* ext, int* out_count) { +static int bux_count_files_recursive(const char* dir, const char* ext, size_t ext_len) { DIR* d = opendir(dir); - if (!d) { *out_count = 0; return NULL; } - - /* First pass: count matching files */ + if (!d) return 0; int count = 0; - size_t ext_len = strlen(ext); struct dirent* entry; while ((entry = readdir(d)) != NULL) { - size_t name_len = strlen(entry->d_name); - if (name_len > ext_len && strcmp(entry->d_name + name_len - ext_len, ext) == 0) { - count++; - } - } - - /* Allocate result array */ - char** result = (char**)malloc(count * sizeof(char*)); - if (!result) { closedir(d); *out_count = 0; return NULL; } - - /* Second pass: collect paths */ - rewinddir(d); - int idx = 0; - size_t dir_len = strlen(dir); - while ((entry = readdir(d)) != NULL && idx < count) { - size_t name_len = strlen(entry->d_name); - if (name_len > ext_len && strcmp(entry->d_name + name_len - ext_len, ext) == 0) { - /* Build full path: dir/name */ - size_t path_len = dir_len + 1 + name_len + 1; - char* path = (char*)malloc(path_len); - if (path) { - snprintf(path, path_len, "%s/%s", dir, entry->d_name); - result[idx++] = path; + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; + size_t path_len = strlen(dir) + 1 + strlen(entry->d_name) + 1; + char* path = (char*)malloc(path_len); + if (!path) continue; + snprintf(path, path_len, "%s/%s", dir, entry->d_name); + struct stat st; + if (stat(path, &st) == 0) { + if (S_ISDIR(st.st_mode)) { + count += bux_count_files_recursive(path, ext, ext_len); + } else { + size_t name_len = strlen(entry->d_name); + if (name_len > ext_len && strcmp(entry->d_name + name_len - ext_len, ext) == 0) { + count++; + } } } + free(path); + } + closedir(d); + return count; +} + +static int bux_collect_files_recursive(const char* dir, const char* ext, size_t ext_len, char** result, int idx) { + DIR* d = opendir(dir); + if (!d) return idx; + struct dirent* entry; + while ((entry = readdir(d)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; + size_t path_len = strlen(dir) + 1 + strlen(entry->d_name) + 1; + char* path = (char*)malloc(path_len); + if (!path) continue; + snprintf(path, path_len, "%s/%s", dir, entry->d_name); + struct stat st; + if (stat(path, &st) == 0) { + if (S_ISDIR(st.st_mode)) { + idx = bux_collect_files_recursive(path, ext, ext_len, result, idx); + free(path); + } else { + size_t name_len = strlen(entry->d_name); + if (name_len > ext_len && strcmp(entry->d_name + name_len - ext_len, ext) == 0) { + result[idx++] = path; + } else { + free(path); + } + } + } else { + free(path); + } } closedir(d); + return idx; +} + +char** bux_list_dir(const char* dir, const char* ext, int* out_count) { + size_t ext_len = strlen(ext); + int count = bux_count_files_recursive(dir, ext, ext_len); + if (count == 0) { *out_count = 0; return NULL; } + char** result = (char**)malloc(count * sizeof(char*)); + if (!result) { *out_count = 0; return NULL; } + int idx = bux_collect_files_recursive(dir, ext, ext_len, result, 0); *out_count = idx; return result; } diff --git a/src/ast.bux b/src/ast.bux index 644e517..6d3fce1 100644 --- a/src/ast.bux +++ b/src/ast.bux @@ -265,6 +265,7 @@ struct Decl { variant5: EnumVariant, variant6: EnumVariant, variant7: EnumVariant, + variant8: EnumVariant, // Impl methods (up to 4) methodCount: int, // Use/import diff --git a/src/hir_lower.bux b/src/hir_lower.bux index d25216c..c6d07f2 100644 --- a/src/hir_lower.bux +++ b/src/hir_lower.bux @@ -1006,6 +1006,7 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule { if vi == 5 { v = &decl.variant5; } if vi == 6 { v = &decl.variant6; } if vi == 7 { v = &decl.variant7; } + if vi == 8 { v = &decl.variant8; } if v != null as *EnumVariant { hm.enums[ei].variants[vi].name = v.name; hm.enums[ei].variants[vi].fieldCount = v.fieldCount; diff --git a/src/parser.bux b/src/parser.bux index 0f99157..ddc314e 100644 --- a/src/parser.bux +++ b/src/parser.bux @@ -236,6 +236,9 @@ func parserMakeExpr(kind: int, line: uint32, col: uint32) -> *Expr { // --------------------------------------------------------------------------- func parserParsePrimary(p: *Parser) -> *Expr { + while parserCheck(p, tkNewLine) { + discard parserAdvance(p); + } let tok: LexToken = parserCurToken(p); let line: uint32 = tok.line; let col: uint32 = tok.column; @@ -572,6 +575,9 @@ func parserPrecedence(op: int) -> int { } func parserParseUnary(p: *Parser) -> *Expr { + while parserCheck(p, tkNewLine) { + discard parserAdvance(p); + } let tok: LexToken = parserCurToken(p); let line: uint32 = tok.line; let col: uint32 = tok.column; @@ -592,6 +598,9 @@ func parserParseUnary(p: *Parser) -> *Expr { func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr { var left: *Expr = parserParseUnary(p); while true { + while parserCheck(p, tkNewLine) { + discard parserAdvance(p); + } let op: int = parserPeek(p, 0); let prec: int = parserPrecedence(op); if prec < minPrec { break; } @@ -1851,7 +1860,7 @@ func parserParseEnumDecl(p: *Parser, isPublic: bool) -> *Decl { discard parserExpect(p, tkLBrace, "expected '{'"); while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile { - if d.variantCount >= 8 { break; } + if d.variantCount >= 9 { break; } if parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) { discard parserAdvance(p); continue; } let vName: LexToken = parserExpect(p, tkIdent, "expected variant name"); @@ -1882,6 +1891,7 @@ func parserParseEnumDecl(p: *Parser, isPublic: bool) -> *Decl { else if d.variantCount == 5 { d.variant5 = v; } else if d.variantCount == 6 { d.variant6 = v; } else if d.variantCount == 7 { d.variant7 = v; } + else if d.variantCount == 8 { d.variant8 = v; } d.variantCount = d.variantCount + 1; parserMatch(p, tkComma); diff --git a/src/sema.bux b/src/sema.bux index 569ec9b..72ac1ae 100644 --- a/src/sema.bux +++ b/src/sema.bux @@ -490,7 +490,7 @@ func Sema_CollectGlobals(sema: *Sema) { // Register enum variants as constants var vi: int = 0; - while vi < decl.variantCount && vi < 8 { + while vi < decl.variantCount && vi < 9 { var v: EnumVariant; if vi == 0 { v = decl.variant0; } else if vi == 1 { v = decl.variant1; } @@ -500,6 +500,7 @@ func Sema_CollectGlobals(sema: *Sema) { else if vi == 5 { v = decl.variant5; } else if vi == 6 { v = decl.variant6; } else if vi == 7 { v = decl.variant7; } + else if vi == 8 { v = decl.variant8; } if v.name == null as String || String_Eq(v.name, "") { vi = vi + 1; continue;