From 3b7fc455b0c00385e28f4d51a01599ad1eeae42c Mon Sep 17 00:00:00 2001 From: dimgigov Date: Sun, 7 Jun 2026 23:02:43 +0300 Subject: [PATCH] fix(parser): infinite loop on newlines in multi-import, multi-line params, type args, struct patterns Parser would hang (infinite loop) when encountering newline tokens inside certain constructs because expect() returns without advancing position when the expected token doesn't match. Fixed 5 places in parseBaseType, parsePrimaryPattern, parseTypeParams, parseParamList, and parseUseDecl by adding newline-skipping at the start of each iteration. Verified: compiler no longer hangs/crashes on all 3 real-world apps from apps/ (boko-framework, jwt-pitbul, nexus). --- README.md | 15 +++++++++++++++ bootstrap/parser.nim | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/README.md b/README.md index d6a7b6d..6c4559d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ > **Selfhost loop:** `buxc2` can compile itself — deterministic C codegen verified. > **LIR backend** produces clean 3-address C code. All 26 examples pass. > **Gradual Ownership:** `@[Checked]` borrow checker, `borrow &mut` expressions, `@[Shared]` attribute. +> **Apps tested:** compiler successfully parses all 3 real-world apps (`apps/boko-framework`, `apps/jwt-pitbul`, `apps/nexus`) — no hangs, no crashes. Bux is a fast, compiled, strongly-typed systems programming language. Features a C backend for native code generation, raw multi-line strings, gradual ownership (opt-in borrow checking), async/await, generics, algebraic enums, and a package manager. @@ -254,6 +255,20 @@ make clean --- +## Applications + +The `apps/` directory contains real-world Bux applications that serve as integration tests for the compiler: + +| App | Description | Lines | +|-----|-------------|-------| +| **boko-framework** | Async web framework (like FastAPI), multi-threaded HTTP server | ~660 | +| **jwt-pitbul** | JWT CLI tool — sign, verify, decode (HS256/384/512, RS256/384/512, ES256/384, EdDSA) | ~326 | +| **nexus** | High-performance HTTP/1.1, HTTP/2 & WebSocket server | ~550 | + +The bootstrap compiler successfully parses and type-checks all three applications without hanging or crashing. + +--- + ## Documentation - [`docs/LanguageRef.md`](docs/LanguageRef.md) — Language reference diff --git a/bootstrap/parser.nim b/bootstrap/parser.nim index 25d9567..7213c83 100644 --- a/bootstrap/parser.nim +++ b/bootstrap/parser.nim @@ -204,6 +204,10 @@ proc parseBaseType(p: var Parser): TypeExpr = var typeArgs: seq[TypeExpr] = @[] discard p.advance() while not p.check(tkGt) and not p.isAtEnd: + while p.check(tkNewLine): + discard p.advance() + if p.check(tkGt) or p.isAtEnd: + break typeArgs.add(p.parseType()) if p.check(tkComma): discard p.advance() @@ -310,6 +314,10 @@ proc parsePrimaryPattern(p: var Parser): Pattern = discard p.advance() var fields: seq[tuple[name: string, pattern: Pattern]] = @[] while not p.check(tkRBrace) and not p.isAtEnd: + while p.check(tkNewLine): + discard p.advance() + if p.check(tkRBrace) or p.isAtEnd: + break let fieldName = p.expect(tkIdent, "expected field name in struct pattern").text discard p.expect(tkColon, "expected ':' after field name in pattern") fields.add((fieldName, p.parsePattern())) @@ -965,6 +973,10 @@ proc parseTypeParams(p: var Parser): seq[TypeParam] = if p.check(tkLt): discard p.advance() while not p.check(tkGt) and not p.isAtEnd: + while p.check(tkNewLine): + discard p.advance() + if p.check(tkGt) or p.isAtEnd: + break var name = "" var isLifetime = false if p.check(tkIdent): @@ -997,6 +1009,10 @@ proc parseTypeParams(p: var Parser): seq[TypeParam] = proc parseParamList(p: var Parser, allowVariadic: bool = false): seq[Param] = discard p.expect(tkLParen, "expected '('") while not p.check(tkRParen) and not p.isAtEnd: + while p.check(tkNewLine): + discard p.advance() + if p.check(tkRParen) or p.isAtEnd: + break let loc = p.currentLoc var isVar = false if allowVariadic and p.check(tkDotDotDot): @@ -1261,6 +1277,10 @@ proc parseUseDecl(p: var Parser, attrs: ParsedAttrs): Decl = if p.check(tkLBrace): discard p.advance() while not p.check(tkRBrace) and not p.isAtEnd: + while p.check(tkNewLine): + discard p.advance() + if p.check(tkRBrace) or p.isAtEnd: + break names.add(p.expect(tkIdent, "expected name in multi-import").text) if p.check(tkComma): discard p.advance()