Phase 7.9: Self-hosted compiler buxc2 builds and runs! 🎉

buxc (Nim bootstrap) successfully compiles buxc2 (Bux self-hosted compiler)
into a working 88KB ELF x86-64 binary.

Compiler fixes (Nim):
- duplicate symbol: user funcs shadow stdlib funcs via mergeDecls()
- forward declarations: func without body + definition (both orderings)
- extern func dedup: same extern in multiple files
- discard keyword: new language keyword, lowered to expr stmt or no-op
- parser: keywords as field names + advance-on-error safeguard
- parser: var without initializer (zero-init)
- parser: multi-line || && continuation expressions
- parser: else-if chain newline handling
- C backend: const declarations emitted as #define
- C backend: load(field_ptr) → base.field optimization (fixes lvalue errors)

Source fixes (src_bux/*.bux):
- types.bux: tk* → ty* prefix for type kind constants (avoid token conflict)
- sema.bux: remaining tk* → ty* references, StringMap → *void workaround
- hir_lower.bux: ekReturn removed, Lcx_LowerParam helper, *f dereference
- c_backend.bux: &mod.funcs[i] for pointer passing
- cli.bux: ReadFile/WriteFile → bux_read_file/bux_write_file wrappers
- parser.bux: pathStr.len → String_Len(pathStr)
- types.bux: "*" + x → String_Concat("*", x)

Build system:
- Makefile: added 4 missing examples + selfhost target
- PLAN.md: updated with actual project state, Phase 7.9 marked complete

All 18 examples pass, all unit tests pass.
This commit is contained in:
2026-05-31 16:34:36 +03:00
parent 166954204c
commit cb256397bd
31 changed files with 4603 additions and 284 deletions
+55 -4
View File
@@ -93,6 +93,25 @@ proc expect(p: var Parser, kind: TokenKind, message: string): Token =
))
result = tok
proc isKeywordToken(kind: TokenKind): bool =
return kind in {tkIf, tkElse, tkWhile, tkDo, tkLoop, tkFor, tkIn, tkBreak,
tkContinue, tkReturn, tkMatch, tkFunc, tkLet, tkVar, tkConst, tkType,
tkStruct, tkEnum, tkUnion, tkInterface, tkExtend, tkModule, tkImport,
tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkSizeOf, tkOwn,
tkDiscard}
proc expectIdentOrKeyword(p: var Parser, message: string): Token =
## Accept identifier OR keyword token as a name (for field names, param names, etc.)
if p.check(tkIdent) or isKeywordToken(p.at.kind):
return p.advance()
let tok = p.at
p.diagnostics.add(ParserDiagnostic(
severity: pdsError,
loc: tok.loc,
message: message & " (got " & tokenKindName(tok.kind) & ")"
))
result = tok
proc previous(p: Parser): Token =
if p.pos > 0 and p.pos <= p.tokens.len:
return p.tokens[p.pos - 1]
@@ -104,6 +123,9 @@ proc currentLoc(p: Parser): SourceLocation =
proc emitError(p: var Parser, loc: SourceLocation, message: string) =
p.diagnostics.add(ParserDiagnostic(severity: pdsError, loc: loc, message: message))
proc skipNewlines(p: var Parser) =
while p.check(tkNewLine): discard p.advance()
proc emitError(p: var Parser, message: string) =
p.emitError(p.currentLoc, message)
@@ -492,7 +514,7 @@ proc parsePostfix(p: var Parser): Expr =
of tkDot:
# Field expression
discard p.advance()
let fieldName = p.expect(tkIdent, "expected field name after '.'").text
let fieldName = p.expectIdentOrKeyword("expected field name after '.'").text
left = Expr(kind: ekField, loc: loc, exprFieldObj: left, exprFieldName: fieldName)
of tkPlusPlus, tkMinusMinus:
let op = p.advance().kind
@@ -656,19 +678,25 @@ proc parseBitOr(p: var Parser): Expr =
proc parseAnd(p: var Parser): Expr =
let loc = p.currentLoc
var left = p.parseBitOr()
p.skipNewlines()
while p.check(tkAmpAmp):
let op = p.advance().kind
p.skipNewlines()
let right = p.parseBitOr()
left = newBinaryExpr(op, left, right, loc)
p.skipNewlines()
return left
proc parseOr(p: var Parser): Expr =
let loc = p.currentLoc
var left = p.parseAnd()
p.skipNewlines()
while p.check(tkPipePipe):
let op = p.advance().kind
p.skipNewlines()
let right = p.parseAnd()
left = newBinaryExpr(op, left, right, loc)
p.skipNewlines()
return left
proc parseTernary(p: var Parser): Expr =
@@ -725,8 +753,12 @@ proc parseStmt(p: var Parser): Stmt =
if p.check(tkColon):
discard p.advance()
ty = p.parseType()
discard p.expect(tkAssign, "expected '=' in let/var statement")
let initExpr = p.parseExpr()
var initExpr: Expr = nil
if p.check(tkAssign):
discard p.advance()
initExpr = p.parseExpr()
elif not isMut:
discard p.expect(tkAssign, "expected '=' in let statement")
if p.check(tkSemicolon):
discard p.advance()
return Stmt(kind: skLet, loc: loc, stmtLetMut: isMut, stmtLetName: name,
@@ -749,6 +781,7 @@ proc parseStmt(p: var Parser): Stmt =
let elifCond = p.parseExpr()
let elifBlk = p.parseBlock()
elseIfs.add(ElseIf(loc: elseLoc, cond: elifCond, blk: elifBlk))
while p.check(tkNewLine): discard p.advance()
else:
elseBlk = p.parseBlock()
break
@@ -831,6 +864,20 @@ proc parseStmt(p: var Parser): Stmt =
if p.check(tkSemicolon):
discard p.advance()
return Stmt(kind: skContinue, loc: loc, stmtContinueLabel: label)
of tkDiscard:
discard p.advance()
var val: Expr = nil
if not p.check(tkSemicolon) and not p.check(tkRBrace) and not p.check(tkNewLine):
val = p.parseExpr()
if p.check(tkSemicolon):
discard p.advance()
# discard expr → expression statement; discard; → no-op (nil expr)
if val != nil:
return Stmt(kind: skExpr, loc: loc, stmtExpr: val)
else:
# No-op: emit literal 0 as expression statement
let zeroTok = Token(kind: tkIntLiteral, text: "0", loc: loc)
return Stmt(kind: skExpr, loc: loc, stmtExpr: Expr(kind: ekLiteral, loc: loc, exprLit: zeroTok))
of tkFunc, tkStruct, tkEnum, tkUnion, tkInterface, tkExtend, tkModule,
tkImport, tkConst, tkType, tkExtern, tkPub:
# Local declaration
@@ -931,17 +978,21 @@ proc parseStructDecl(p: var Parser, isPublic: bool): Decl =
discard p.advance()
if p.check(tkRBrace) or p.isAtEnd:
break
let startPos = p.pos # Track position for infinite-loop safeguard
let fLoc = p.currentLoc
var fPub = false
if p.check(tkPub):
fPub = true
discard p.advance()
let fName = p.expect(tkIdent, "expected field name").text
let fName = p.expectIdentOrKeyword("expected field name").text
discard p.expect(tkColon, "expected ':' after field name")
let fType = p.parseType()
if p.check(tkSemicolon) or p.check(tkComma):
discard p.advance()
fields.add(StructField(loc: fLoc, isPublic: fPub, name: fName, ftype: fType))
# Infinite-loop safeguard: if no progress, advance
if p.pos == startPos:
discard p.advance()
discard p.expect(tkRBrace, "expected '}' to close struct")
return Decl(kind: dkStruct, loc: loc, isPublic: isPublic,
declStructName: name, declStructTypeParams: typeParams,