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:
@@ -188,6 +188,19 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
|
||||
return &"({callee})({argsStr})"
|
||||
|
||||
of hLoad:
|
||||
# Optimize: load(field_ptr(base, field)) → base.field (avoids & on temporaries)
|
||||
if node.loadPtr != nil and node.loadPtr.kind == hFieldPtr:
|
||||
let base = be.emitExpr(node.loadPtr.fieldPtrBase)
|
||||
return &"({base}.{node.loadPtr.fieldName})"
|
||||
# Optimize: load(arrow_field(base, field)) → base->field
|
||||
if node.loadPtr != nil and node.loadPtr.kind == hArrowField:
|
||||
let base = be.emitExpr(node.loadPtr.arrowFieldBase)
|
||||
return &"({base}->{node.loadPtr.arrowFieldName})"
|
||||
# Optimize: load(index_ptr(base, idx)) → base[idx]
|
||||
if node.loadPtr != nil and node.loadPtr.kind == hIndexPtr:
|
||||
let base = be.emitExpr(node.loadPtr.indexPtrBase)
|
||||
let idx = be.emitExpr(node.loadPtr.indexPtrIndex)
|
||||
return &"({base}[{idx}])"
|
||||
let ptrExpr = be.emitExpr(node.loadPtr)
|
||||
return &"(*{ptrExpr})"
|
||||
|
||||
@@ -482,6 +495,26 @@ proc emitModule*(be: var CBackend, module: HirModule): string =
|
||||
be.emitExternDecl(ef)
|
||||
be.emitLine("")
|
||||
|
||||
# Const declarations as #define
|
||||
if module.consts.len > 0:
|
||||
be.emitLine("/* Constants */")
|
||||
for c in module.consts:
|
||||
let val = c.value
|
||||
if val != nil and val.kind == hLit:
|
||||
let tok = val.litToken
|
||||
case tok.kind
|
||||
of tkIntLiteral:
|
||||
be.emitLine(&"#define {c.name} {tok.text}")
|
||||
of tkStringLiteral:
|
||||
be.emitLine(&"#define {c.name} \"{tok.text}\"")
|
||||
of tkBoolLiteral:
|
||||
be.emitLine(&"#define {c.name} {tok.text}")
|
||||
else:
|
||||
be.emitLine(&"/* const {c.name} (unsupported literal kind) */")
|
||||
else:
|
||||
be.emitLine(&"/* const {c.name} (complex expression) */")
|
||||
be.emitLine("")
|
||||
|
||||
# Struct definitions
|
||||
for s in module.structs:
|
||||
be.emitStruct(s.name, s.fields)
|
||||
|
||||
+37
-11
@@ -1,4 +1,4 @@
|
||||
import std/[os, strutils, terminal, strformat, osproc]
|
||||
import std/[os, strutils, terminal, strformat, osproc, sets]
|
||||
import lexer, parser, ast, sema, manifest, hir, hir_lower, c_backend, types, scope
|
||||
|
||||
type
|
||||
@@ -142,6 +142,8 @@ Output = "Bin"
|
||||
return 0
|
||||
|
||||
proc collectStdlibDecls(stdlibDir: string): seq[Decl]
|
||||
proc getDeclName(d: Decl): string
|
||||
proc mergeDecls(stdlibDecls: seq[Decl], userDecls: seq[Decl]): seq[Decl]
|
||||
|
||||
proc cmdCheck*(args: seq[string], opts: GlobalOptions): int =
|
||||
let useColor = shouldUseColor(opts)
|
||||
@@ -203,13 +205,11 @@ proc cmdCheck*(args: seq[string], opts: GlobalOptions): int =
|
||||
stdlibDir = path
|
||||
break
|
||||
let stdlibDecls = collectStdlibDecls(stdlibDir)
|
||||
var mergedItems = stdlibDecls
|
||||
for decl in allModuleItems:
|
||||
mergedItems.add(decl)
|
||||
|
||||
let mergedItems = mergeDecls(stdlibDecls, allModuleItems)
|
||||
|
||||
var unifiedModule = newModule("main")
|
||||
unifiedModule.items = mergedItems
|
||||
|
||||
|
||||
let semaRes = analyze(unifiedModule)
|
||||
if semaRes.hasErrors:
|
||||
printError("type errors in project", useColor)
|
||||
@@ -239,6 +239,34 @@ proc collectStdlibDecls(stdlibDir: string): seq[Decl] =
|
||||
else:
|
||||
result.add(item)
|
||||
|
||||
proc getDeclName(d: Decl): string =
|
||||
case d.kind
|
||||
of dkFunc: d.declFuncName
|
||||
of dkExternFunc: d.declExtFuncName
|
||||
of dkStruct: d.declStructName
|
||||
of dkEnum: d.declEnumName
|
||||
of dkUnion: d.declUnionName
|
||||
of dkInterface: d.declInterfaceName
|
||||
of dkConst: d.declConstName
|
||||
of dkTypeAlias: d.declAliasName
|
||||
else: ""
|
||||
|
||||
proc mergeDecls(stdlibDecls: seq[Decl], userDecls: seq[Decl]): seq[Decl] =
|
||||
## Merge stdlib and user declarations.
|
||||
## User funcs shadow stdlib funcs with the same name (simple overload avoidance).
|
||||
var userNames: HashSet[string]
|
||||
for d in userDecls:
|
||||
let name = getDeclName(d)
|
||||
if name != "":
|
||||
userNames.incl(name)
|
||||
result = @[]
|
||||
for d in stdlibDecls:
|
||||
let name = getDeclName(d)
|
||||
if name == "" or name notin userNames:
|
||||
result.add(d)
|
||||
for d in userDecls:
|
||||
result.add(d)
|
||||
|
||||
proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
|
||||
let useColor = shouldUseColor(opts)
|
||||
let root = getCurrentDir()
|
||||
@@ -308,14 +336,12 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
|
||||
return 1
|
||||
|
||||
# Phase 2: Merge all project declarations with stdlib
|
||||
var mergedItems = stdlibDecls
|
||||
for decl in allModuleItems:
|
||||
mergedItems.add(decl)
|
||||
|
||||
let mergedItems = mergeDecls(stdlibDecls, allModuleItems)
|
||||
|
||||
# Create unified module
|
||||
var unifiedModule = newModule("main")
|
||||
unifiedModule.items = mergedItems
|
||||
|
||||
|
||||
# Phase 3: Sema + HIR + C codegen on unified module
|
||||
let (semaRes, semaCtx) = analyzeFull(unifiedModule)
|
||||
if semaRes.hasErrors:
|
||||
|
||||
+10
-5
@@ -773,7 +773,9 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
return ctx.flushPending(ctx.lowerExpr(stmt.stmtExpr))
|
||||
|
||||
of skLet:
|
||||
let initHir = ctx.lowerExpr(stmt.stmtLetInit)
|
||||
var initHir: HirNode = nil
|
||||
if stmt.stmtLetInit != nil:
|
||||
initHir = ctx.lowerExpr(stmt.stmtLetInit)
|
||||
let allocaType = if stmt.stmtLetType != nil:
|
||||
case stmt.stmtLetType.kind
|
||||
of tekNamed:
|
||||
@@ -785,16 +787,17 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
let elemType = ctx.resolveTypeExpr(stmt.stmtLetType.sliceElement)
|
||||
makeSlice(elemType)
|
||||
else: makeUnknown()
|
||||
else:
|
||||
elif stmt.stmtLetInit != nil:
|
||||
ctx.resolveExprType(stmt.stmtLetInit)
|
||||
else:
|
||||
makeUnknown()
|
||||
|
||||
let alloca = hirAlloca(stmt.stmtLetName, allocaType, loc)
|
||||
let varNode = hirVar(stmt.stmtLetName, makePointer(allocaType), loc)
|
||||
let store = hirStore(varNode, initHir, loc)
|
||||
# Track type expr for generic method inference
|
||||
if stmt.stmtLetType != nil:
|
||||
ctx.varTypeExprs[stmt.stmtLetName] = stmt.stmtLetType
|
||||
elif stmt.stmtLetInit.kind == ekStructInit and stmt.stmtLetInit.exprStructInitTypeArgs.len > 0:
|
||||
elif stmt.stmtLetInit != nil and stmt.stmtLetInit.kind == ekStructInit and stmt.stmtLetInit.exprStructInitTypeArgs.len > 0:
|
||||
ctx.varTypeExprs[stmt.stmtLetName] = TypeExpr(
|
||||
kind: tekNamed,
|
||||
loc: stmt.stmtLetInit.loc,
|
||||
@@ -804,7 +807,9 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
var stmts = ctx.pendingStmts
|
||||
ctx.pendingStmts = @[]
|
||||
stmts.add(alloca)
|
||||
stmts.add(store)
|
||||
if initHir != nil:
|
||||
let store = hirStore(varNode, initHir, loc)
|
||||
stmts.add(store)
|
||||
return hirBlock(stmts, nil, makeVoid(), loc)
|
||||
|
||||
of skReturn:
|
||||
|
||||
+55
-4
@@ -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,
|
||||
|
||||
+23
-4
@@ -209,7 +209,19 @@ proc collectGlobals*(sema: var Sema) =
|
||||
let retType = if decl.declFuncReturnType != nil: sema.resolveType(decl.declFuncReturnType) else: makeVoid()
|
||||
sym.typ = makeFunc(params, retType)
|
||||
if not sema.globalScope.define(sym):
|
||||
sema.emitError(decl.loc, &"duplicate symbol '{decl.declFuncName}'")
|
||||
let existing = sema.globalScope.lookup(decl.declFuncName)
|
||||
if existing != nil and existing.kind == skFunc:
|
||||
if existing.decl != nil and existing.decl.declFuncBody == nil and decl.declFuncBody != nil:
|
||||
# First was forward declaration, update with definition
|
||||
existing.decl = decl
|
||||
existing.typ = sym.typ
|
||||
elif decl.declFuncBody == nil:
|
||||
# New one is a forward declaration, existing already has it — skip
|
||||
discard
|
||||
else:
|
||||
sema.emitError(decl.loc, &"duplicate symbol '{decl.declFuncName}'")
|
||||
else:
|
||||
sema.emitError(decl.loc, &"duplicate symbol '{decl.declFuncName}'")
|
||||
# Auto-register func Type_Method(self: Type, ...) as a method
|
||||
if decl.declFuncParams.len > 0 and decl.declFuncParams[0].name == "self":
|
||||
var typeName = ""
|
||||
@@ -243,7 +255,10 @@ proc collectGlobals*(sema: var Sema) =
|
||||
let retType = if decl.declExtFuncReturnType != nil: sema.resolveType(decl.declExtFuncReturnType) else: makeVoid()
|
||||
sym.typ = makeFunc(params, retType)
|
||||
if not sema.globalScope.define(sym):
|
||||
sema.emitError(decl.loc, &"duplicate symbol '{decl.declExtFuncName}'")
|
||||
# Allow duplicate extern func declarations (same func declared in multiple files)
|
||||
let existing = sema.globalScope.lookup(decl.declExtFuncName)
|
||||
if existing == nil or existing.kind != skFunc:
|
||||
sema.emitError(decl.loc, &"duplicate symbol '{decl.declExtFuncName}'")
|
||||
of dkStruct:
|
||||
let t = makeNamed(decl.declStructName)
|
||||
let sym = Symbol(kind: skType, name: decl.declStructName, typ: t,
|
||||
@@ -776,10 +791,14 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
|
||||
of skExpr:
|
||||
return sema.checkExpr(stmt.stmtExpr, scope)
|
||||
of skLet:
|
||||
let initType = sema.checkExpr(stmt.stmtLetInit, scope)
|
||||
var initType: Type = makeVoid()
|
||||
if stmt.stmtLetInit != nil:
|
||||
initType = sema.checkExpr(stmt.stmtLetInit, scope)
|
||||
let declaredType = if stmt.stmtLetType != nil: sema.resolveType(stmt.stmtLetType) else: initType
|
||||
if stmt.stmtLetType != nil and not initType.isAssignableTo(declaredType) and not (initType.kind in {TypeKind.tkUnknown, TypeKind.tkNamed, TypeKind.tkTypeParam}):
|
||||
if stmt.stmtLetInit != nil and stmt.stmtLetType != nil and not initType.isAssignableTo(declaredType) and not (initType.kind in {TypeKind.tkUnknown, TypeKind.tkNamed, TypeKind.tkTypeParam}):
|
||||
sema.emitError(stmt.loc, &"cannot assign {initType.toString} to {declaredType.toString}")
|
||||
if stmt.stmtLetInit == nil and stmt.stmtLetType == nil:
|
||||
sema.emitError(stmt.loc, "variable must have either type annotation or initializer")
|
||||
let sym = Symbol(kind: skVar, name: stmt.stmtLetName, typ: declaredType,
|
||||
isMutable: stmt.stmtLetMut)
|
||||
if not scope.define(sym):
|
||||
|
||||
+4
-1
@@ -50,6 +50,7 @@ type
|
||||
tkSuper # super
|
||||
tkSizeOf # sizeof
|
||||
tkOwn # own (gradual ownership transfer)
|
||||
tkDiscard # discard (evaluate and throw away)
|
||||
|
||||
##Punctuation
|
||||
tkLParen # (
|
||||
@@ -141,7 +142,7 @@ proc isKeyword*(kind: TokenKind): bool =
|
||||
tkBreak, tkContinue, tkReturn, tkMatch,
|
||||
tkFunc, tkLet, tkVar, tkConst, tkType, tkStruct, tkEnum,
|
||||
tkUnion, tkInterface, tkExtend, tkModule, tkImport,
|
||||
tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkOwn:
|
||||
tkPub, tkExtern, tkAs, tkIs, tkNull, tkSelf, tkSuper, tkOwn, tkDiscard:
|
||||
true
|
||||
else:
|
||||
false
|
||||
@@ -196,6 +197,7 @@ proc keywordKind*(text: string): TokenKind =
|
||||
of "super": tkSuper
|
||||
of "sizeof": tkSizeOf
|
||||
of "own": tkOwn
|
||||
of "discard": tkDiscard
|
||||
of "true", "false": tkBoolLiteral
|
||||
else: tkIdent
|
||||
|
||||
@@ -240,6 +242,7 @@ proc tokenKindName*(kind: TokenKind): string =
|
||||
of tkSelf: "'self'"
|
||||
of tkSuper: "'super'"
|
||||
of tkOwn: "'own'"
|
||||
of tkDiscard: "'discard'"
|
||||
of tkLParen: "'('"
|
||||
of tkRParen: "')'"
|
||||
of tkLBrace: "'{'"
|
||||
|
||||
Reference in New Issue
Block a user