feat: restructure repo, borrow checker, expanded stdlib

- Reorganize repository to Rust-style layout:
  compiler/bootstrap/  compiler/selfhost/  compiler/tests/
  library/std/  library/runtime/  tests/  tools/
- Add buxs/ Windows-compatible project root
- Add borrow checker tests and implement:
  - Alias analysis (double mutable borrow detection)
  - Use-after-move detection for own T
- Expand standard library:
  - Std::Os: Args, Env, Cwd, Chdir
  - Std::Time: NowMs, NowUs, SleepMs
  - Std::Process: Run, Output
  - Std::Io: PrintInt64 (fixes 32-bit truncation bug)
- Add examples: os_time.bux, process.bux
- Fix PrintInt to use int64_t in C runtime
This commit is contained in:
2026-06-05 20:08:17 +03:00
parent a45a2ecd49
commit 9c6b516453
75 changed files with 581 additions and 79 deletions
+443
View File
@@ -0,0 +1,443 @@
import source_location, token
type
CallingConvention* = enum
ccDefault
ccWin64
IntrinsicKind* = enum
ikLine
ikColumn
ikFile
ikFunction
ikDate
ikTime
ikModule
UseKind* = enum
ukSingle
ukGlob
ukMulti
# ---------------------------------------------------------------------------
# Type expressions
# ---------------------------------------------------------------------------
TypeExprKind* = enum
tekNamed
tekPath
tekSlice
tekPointer
tekOwn ## own T — owned value (gradual ownership)
tekRef ## &T — shared reference (gradual ownership)
tekMutRef ## &mut T — mutable reference
tekDynRef ## &dyn Trait — trait object (fat pointer)
tekTuple
tekSelf
TypeExpr* = ref object
loc*: SourceLocation
case kind*: TypeExprKind
of tekNamed:
typeName*: string
typeArgs*: seq[TypeExpr]
of tekPath:
pathSegments*: seq[string]
of tekSlice:
sliceElement*: TypeExpr
sliceSize*: Expr ## nil for unsized slices T[]
of tekOwn, tekPointer, tekRef, tekMutRef:
pointerPointee*: TypeExpr
refLifetime*: string ## only meaningful for tekRef/tekMutRef
of tekDynRef:
dynInterface*: string
of tekTuple:
tupleElements*: seq[TypeExpr]
of tekSelf:
discard
# ---------------------------------------------------------------------------
# Patterns
# ---------------------------------------------------------------------------
PatternKind* = enum
pkWildcard
pkLiteral
pkIdent
pkRange
pkEnum
pkStruct
pkTuple
pkGuarded
Pattern* = ref object
loc*: SourceLocation
case kind*: PatternKind
of pkWildcard:
discard
of pkLiteral:
patLit*: Token
of pkIdent:
patIdent*: string
of pkRange:
patRangeLo*: Pattern
patRangeHi*: Pattern
patRangeInclusive*: bool
of pkEnum:
patEnumPath*: seq[string]
patEnumArgs*: seq[Pattern]
patEnumNamed*: seq[tuple[name: string, pattern: Pattern]]
of pkStruct:
patStructName*: string
patStructFields*: seq[tuple[name: string, pattern: Pattern]]
of pkTuple:
patTupleElements*: seq[Pattern]
of pkGuarded:
patGuardedInner*: Pattern
patGuardedExpr*: Expr
# ---------------------------------------------------------------------------
# Expressions
# ---------------------------------------------------------------------------
ExprKind* = enum
ekLiteral
ekIdent
ekSelf
ekPath
ekSizeOf
ekIntrinsic
ekUnary
ekPostfix
ekBinary
ekAssign
ekTernary
ekRange
ekCall
ekGenericCall
ekIndex
ekField
ekStructInit
ekSlice
ekSpread
ekTuple
ekCast
ekIs
ekTry
ekUnwrap ## expr! — unwrap or panic
ekSpawn ## spawn expr — create a new task
ekAwait ## expr.await — suspend until future resolves
ekBlock
ekMatch
MatchArm* = object
loc*: SourceLocation
pattern*: Pattern
body*: Expr
Expr* = ref object
loc*: SourceLocation
case kind*: ExprKind
of ekLiteral:
exprLit*: Token
of ekIdent:
exprIdent*: string
of ekSelf:
discard
of ekPath:
exprPath*: seq[string]
of ekSizeOf:
exprSizeOfType*: TypeExpr
of ekIntrinsic:
exprIntrinsic*: IntrinsicKind
of ekUnary:
exprUnaryOp*: TokenKind
exprUnaryOperand*: Expr
of ekPostfix:
exprPostfixOp*: TokenKind
exprPostfixOperand*: Expr
of ekBinary:
exprBinaryOp*: TokenKind
exprBinaryLeft*: Expr
exprBinaryRight*: Expr
of ekAssign:
exprAssignOp*: TokenKind
exprAssignTarget*: Expr
exprAssignValue*: Expr
of ekTernary:
exprTernaryCond*: Expr
exprTernaryThen*: Expr
exprTernaryElse*: Expr
of ekRange:
exprRangeLo*: Expr
exprRangeHi*: Expr
exprRangeInclusive*: bool
of ekCall:
exprCallCallee*: Expr
exprCallArgs*: seq[Expr]
exprCallInferredTypeArgs*: seq[TypeExpr] ## filled by sema for inferred generic calls
of ekGenericCall:
exprGenericCallee*: string
exprGenericTypeArgs*: seq[TypeExpr]
of ekIndex:
exprIndexObj*: Expr
exprIndexIdx*: Expr
exprIndexBoundsCheck*: bool
of ekField:
exprFieldObj*: Expr
exprFieldName*: string
of ekStructInit:
exprStructInitName*: string
exprStructInitTypeArgs*: seq[TypeExpr]
exprStructInitFields*: seq[tuple[name: string, value: Expr]]
of ekSlice:
exprSliceElements*: seq[Expr]
of ekSpread:
exprSpreadOperand*: Expr
of ekTuple:
exprTupleElements*: seq[Expr]
of ekCast:
exprCastOperand*: Expr
exprCastType*: TypeExpr
of ekIs:
exprIsOperand*: Expr
exprIsType*: TypeExpr
of ekTry:
exprTryOperand*: Expr
exprTryType*: TypeExpr # nil for Result?, or explicit target type
of ekUnwrap:
exprUnwrapOperand*: Expr
of ekSpawn:
exprSpawnCallee*: Expr
exprSpawnArgs*: seq[Expr]
exprSpawnAsync*: bool
of ekAwait:
exprAwaitOperand*: Expr
of ekBlock:
exprBlock*: Block
of ekMatch:
exprMatchSubject*: Expr
exprMatchArms*: seq[MatchArm]
# ---------------------------------------------------------------------------
# Statements
# ---------------------------------------------------------------------------
StmtKind* = enum
skExpr
skLet
skIf
skWhile
skDoWhile
skLoop
skFor
skMatch
skReturn
skBreak
skContinue
skStaticAssert
skComptime
skEmit
skDecl
ElseIf* = object
loc*: SourceLocation
cond*: Expr
blk*: Block
Block* = ref object
loc*: SourceLocation
stmts*: seq[Stmt]
Stmt* = ref object
loc*: SourceLocation
case kind*: StmtKind
of skExpr:
stmtExpr*: Expr
of skLet:
stmtLetMut*: bool
stmtLetName*: string
stmtLetPattern*: Pattern
stmtLetType*: TypeExpr ## nil if inferred
stmtLetInit*: Expr
of skIf:
stmtIfCond*: Expr
stmtIfThen*: Block
stmtIfElseIfs*: seq[ElseIf]
stmtIfElse*: Block ## nil if no else
of skWhile:
stmtWhileLabel*: string
stmtWhileCond*: Expr
stmtWhileBody*: Block
of skDoWhile:
stmtDoWhileLabel*: string
stmtDoWhileBody*: Block
stmtDoWhileCond*: Expr
of skLoop:
stmtLoopLabel*: string
stmtLoopBody*: Block
of skFor:
stmtForLabel*: string
stmtForVar*: string
stmtForIter*: Expr
stmtForBody*: Block
of skMatch:
stmtMatchSubject*: Expr
stmtMatchArms*: seq[MatchArm]
of skReturn:
stmtReturnValue*: Expr ## nil for bare return
of skBreak:
stmtBreakLabel*: string
of skContinue:
stmtContinueLabel*: string
of skStaticAssert:
stmtStaticAssertCond*: Expr
stmtStaticAssertMsg*: Expr
of skComptime:
stmtComptimeBlock*: Block
of skEmit:
stmtEmitExpr*: Expr
stmtEmitEvaluated*: string ## filled by sema CTFE
of skDecl:
stmtDecl*: Decl
# ---------------------------------------------------------------------------
# Type Parameters (for generics with trait bounds)
# ---------------------------------------------------------------------------
TypeParam* = object
name*: string
bounds*: seq[string] ## e.g. ["Comparable"] for <T: Comparable>
isLifetime*: bool ## true for lifetime params like 'a
# ---------------------------------------------------------------------------
# Declarations
# ---------------------------------------------------------------------------
DeclKind* = enum
dkFunc
dkStruct
dkEnum
dkUnion
dkInterface
dkImpl
dkModule
dkUse
dkConst
dkTypeAlias
dkExternFunc
dkExternVar
dkExternBlock
Param* = object
loc*: SourceLocation
name*: string
ptype*: TypeExpr
isVariadic*: bool
defaultValue*: Expr
StructField* = object
loc*: SourceLocation
isPublic*: bool
name*: string
ftype*: TypeExpr
EnumVariant* = object
loc*: SourceLocation
name*: string
fields*: seq[TypeExpr]
namedFields*: seq[tuple[name: string, ftype: TypeExpr]]
discriminant*: string
UnionField* = object
loc*: SourceLocation
name*: string
ftype*: TypeExpr
Decl* = ref object
loc*: SourceLocation
isPublic*: bool
declAttrs*: seq[string] ## attributes: @[Checked], @[Inline], etc.
case kind*: DeclKind
of dkFunc:
declFuncAsm*: bool
declFuncCallConv*: CallingConvention
declFuncConst*: bool ## const func — evaluable at compile time
declFuncIsAsync*: bool ## async func — returns Future<T>
declFuncName*: string
declFuncTypeParams*: seq[TypeParam]
declFuncParams*: seq[Param]
declFuncReturnType*: TypeExpr ## nil if void/inferred
declFuncBody*: Block ## nil for signature-only
of dkStruct:
declStructName*: string
declStructTypeParams*: seq[TypeParam]
declStructFields*: seq[StructField]
of dkEnum:
declEnumName*: string
declEnumBaseType*: TypeExpr
declEnumVariants*: seq[EnumVariant]
of dkUnion:
declUnionName*: string
declUnionFields*: seq[UnionField]
of dkInterface:
declInterfaceName*: string
declInterfaceAssocTypes*: seq[string] ## associated type names: type Output;
declInterfaceMethods*: seq[Decl] ## FuncDecl signatures only
of dkImpl:
declImplTypeName*: string
declImplTypeParams*: seq[TypeParam] ## type parameters for generic impl: extend Box<T>
declImplInterface*: string ## empty if not for interface
declImplAssocTypes*: seq[tuple[name: string, typ: TypeExpr]] ## type Output = int;
declImplMethods*: seq[Decl]
of dkModule:
declModuleName*: string
declModulePath*: seq[string]
declModuleItems*: seq[Decl]
of dkUse:
declUsePath*: seq[string]
declUseKind*: UseKind
declUseNames*: seq[string] ## for multi-import
declUseTargetOs*: string
of dkConst:
declConstName*: string
declConstType*: TypeExpr
declConstValue*: Expr
of dkTypeAlias:
declAliasName*: string
declAliasType*: TypeExpr
of dkExternFunc:
declExtFuncName*: string
declExtFuncDll*: string
declExtFuncCallConv*: CallingConvention
declExtFuncParams*: seq[Param]
declExtFuncVariadic*: bool
declExtFuncReturnType*: TypeExpr
of dkExternVar:
declExtVarName*: string
declExtVarType*: TypeExpr
of dkExternBlock:
declExtBlockDll*: string
declExtBlockCallConv*: CallingConvention
declExtBlockItems*: seq[Decl]
# ---------------------------------------------------------------------------
# Module (AST root)
# ---------------------------------------------------------------------------
Module* = ref object
name*: string
path*: seq[string]
items*: seq[Decl]
# Convenience constructors
proc newModule*(name: string, path: seq[string] = @[]): Module =
result = Module(name: name, path: path)
proc newBlock*(loc: SourceLocation): Block =
result = Block(loc: loc)
proc newLiteralExpr*(tok: Token): Expr =
result = Expr(kind: ekLiteral, loc: tok.loc, exprLit: tok)
proc newIdentExpr*(name: string, loc: SourceLocation): Expr =
result = Expr(kind: ekIdent, loc: loc, exprIdent: name)
proc newBinaryExpr*(op: TokenKind, left, right: Expr, loc: SourceLocation): Expr =
result = Expr(kind: ekBinary, loc: loc, exprBinaryOp: op, exprBinaryLeft: left, exprBinaryRight: right)
proc newUnaryExpr*(op: TokenKind, operand: Expr, loc: SourceLocation): Expr =
result = Expr(kind: ekUnary, loc: loc, exprUnaryOp: op, exprUnaryOperand: operand)
+708
View File
@@ -0,0 +1,708 @@
import std/[strformat, strutils, tables]
import hir, types, token, source_location
type
CBackend* = object
output*: string
indent*: int
varCounter*: int
declaredVars*: seq[string]
sliceTypeDefs*: seq[tuple[name: string, elem: string]] ## Generated Slice_<T> typedefs
proc cEscape(s: string): string =
## Escape a string for use as a C string literal.
result = ""
for c in s:
case c
of '\\': result.add("\\\\")
of '"': result.add("\\\"")
of '\n': result.add("\\n")
of '\r': result.add("\\r")
of '\t': result.add("\\t")
of '\0': result.add("\\0")
else: result.add(c)
proc initCBackend*(): CBackend =
result.output = ""
result.indent = 0
result.varCounter = 0
result.declaredVars = @[]
proc emit(be: var CBackend, s: string) =
be.output.add(s)
proc emitLine(be: var CBackend, s: string) =
for i in 0..<be.indent:
be.output.add(" ")
be.output.add(s)
be.output.add("\n")
proc emitIndent(be: var CBackend) =
for i in 0..<be.indent:
be.output.add(" ")
proc freshVar(be: var CBackend): string =
inc be.varCounter
result = &"__tmp_{be.varCounter}"
# Type conversion: Bux Type → C type string
proc typeToC*(be: var CBackend, typ: Type): string =
if typ == nil: return "void"
case typ.kind
of tkVoid: return "void"
of tkBool: return "bool"
of tkBool8: return "bool"
of tkBool16: return "bool"
of tkBool32: return "bool"
of tkChar8: return "char"
of tkChar16: return "char16_t"
of tkChar32: return "char32_t"
of tkStr: return "const char*"
of tkInt8: return "int8_t"
of tkInt16: return "int16_t"
of tkInt32: return "int32_t"
of tkInt64: return "int64_t"
of tkInt: return "int"
of tkUInt8: return "uint8_t"
of tkUInt16: return "uint16_t"
of tkUInt32: return "uint32_t"
of tkUInt64: return "uint64_t"
of tkUInt: return "unsigned int"
of tkFloat32: return "float"
of tkFloat64: return "double"
of tkPointer, tkRef, tkMutRef:
if typ.inner.len > 0:
return typeToC(be, typ.inner[0]) & "*"
return "void*"
of tkDynRef:
return typ.name & "_FatPtr"
of tkSlice:
let elemName = if typ.inner.len > 0: typeToC(be, typ.inner[0]) else: "void"
let sliceName = "Slice_" & elemName.replace(" ", "_").replace("*", "Ptr")
var alreadyDefined = false
for d in be.sliceTypeDefs:
if d.name == sliceName:
alreadyDefined = true
break
if not alreadyDefined:
be.sliceTypeDefs.add((name: sliceName, elem: elemName))
return sliceName
of tkNamed:
# Map common Bux type names to C types
case typ.name
of "String", "str": return "const char*"
of "int": return "int"
of "int8": return "int8_t"
of "int16": return "int16_t"
of "int32": return "int32_t"
of "int64": return "int64_t"
of "uint": return "unsigned int"
of "uint8": return "uint8_t"
of "uint16": return "uint16_t"
of "uint32": return "uint32_t"
of "uint64": return "uint64_t"
of "float32": return "float"
of "float64": return "double"
of "bool": return "bool"
else: return typ.name
of tkTuple: return "void*" # TODO: proper tuple struct
of tkFunc: return "void*" # TODO: function pointer
else: return "int"
proc operatorToC(op: TokenKind): string =
case op
of tkPlus: return "+"
of tkMinus: return "-"
of tkStar: return "*"
of tkSlash: return "/"
of tkPercent: return "%"
of tkAmp: return "&"
of tkPipe: return "|"
of tkCaret: return "^"
of tkShl: return "<<"
of tkShr: return ">>"
of tkAmpAmp: return "&&"
of tkPipePipe: return "||"
of tkEq: return "=="
of tkNe: return "!="
of tkLt: return "<"
of tkLe: return "<="
of tkGt: return ">"
of tkGe: return ">="
of tkBang: return "!"
of tkTilde: return "~"
of tkPlusPlus: return "++"
of tkMinusMinus: return "--"
of tkAssign: return "="
of tkPlusAssign: return "+="
of tkMinusAssign: return "-="
of tkStarAssign: return "*="
of tkSlashAssign: return "/="
of tkPercentAssign: return "%="
of tkAmpAssign: return "&="
of tkPipeAssign: return "|="
of tkCaretAssign: return "^="
of tkShlAssign: return "<<="
of tkShrAssign: return ">>="
else: return "?"
# Forward declaration
proc emitExpr(be: var CBackend, node: HirNode): string
proc emitStmt(be: var CBackend, node: HirNode)
proc emitExpr(be: var CBackend, node: HirNode): string =
if node == nil: return "0"
case node.kind
of hLit:
case node.litToken.kind
of tkBoolLiteral:
if node.litToken.text == "true": return "true"
else: return "false"
of tkStringLiteral:
var text = node.litToken.text
# Backtick raw string: strip backticks, escape content for C
if text.len >= 2 and text[0] == '`' and text[text.len-1] == '`':
text = "\"" & cEscape(text[1 ..< text.len-1]) & "\""
# If text has no surrounding quotes, it's from constFoldConstDecl (already unescaped)
elif text.len >= 2 and text[0] == '"' and text[text.len-1] == '"':
# Strip c8" c16" c32" prefixes — in C they are just regular string literals
if text.startsWith("c32\""):
text = "\"" & cEscape(text[4 ..< text.len-1]) & "\""
elif text.startsWith("c16\""):
text = "\"" & cEscape(text[4 ..< text.len-1]) & "\""
elif text.startsWith("c8\""):
text = "\"" & cEscape(text[3 ..< text.len-1]) & "\""
else:
text = "\"" & cEscape(text[1 ..< text.len-1]) & "\""
elif text.len >= 2 and text[0] == '"':
# Partial quote — escape anyway
text = "\"" & cEscape(text[1 ..< text.len]) & "\""
else:
# No quotes — from constFoldConstDecl, needs wrapping and escaping
text = "\"" & cEscape(text) & "\""
return text
of tkNull:
return "NULL"
else:
return node.litToken.text
of hVar:
return node.varName
of hSelf:
return "self"
of hUnary:
let operand = be.emitExpr(node.unaryOperand)
let op = operatorToC(node.unaryOp)
if node.unaryOp == tkStar:
return &"(*{operand})"
elif node.unaryOp == tkAmp:
return &"(&{operand})"
else:
return &"({op}{operand})"
of hBinary:
let left = be.emitExpr(node.binaryLeft)
let right = be.emitExpr(node.binaryRight)
let op = operatorToC(node.binaryOp)
return &"({left} {op} {right})"
of hCall:
var args: seq[string] = @[]
for arg in node.callArgs:
args.add(be.emitExpr(arg))
let argsStr = args.join(", ")
return &"{node.callCallee}({argsStr})"
of hCallIndirect:
let callee = be.emitExpr(node.callIndirectCallee)
var args: seq[string] = @[]
for arg in node.callIndirectArgs:
args.add(be.emitExpr(arg))
let argsStr = args.join(", ")
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})"
of hFieldPtr:
let base = be.emitExpr(node.fieldPtrBase)
return &"(&({base}.{node.fieldName}))"
of hArrowField:
let base = be.emitExpr(node.arrowFieldBase)
return &"(&({base}->{node.arrowFieldName}))"
of hIndexPtr:
let base = be.emitExpr(node.indexPtrBase)
let idx = be.emitExpr(node.indexPtrIndex)
return &"(&({base}[{idx}]))"
of hStructInit:
# C99 compound literal: (StructName){.field1 = val1, .field2 = val2}
var fields: seq[string] = @[]
for f in node.structInitFields:
let val = be.emitExpr(f.value)
fields.add(&".{f.name} = {val}")
let fieldsStr = fields.join(", ")
return &"(({node.structInitName}){{{fieldsStr}}})"
of hSliceInit:
let sliceName = typeToC(be, node.typ)
var elems: seq[string] = @[]
for e in node.sliceInitElements:
elems.add(be.emitExpr(e))
let elemsStr = elems.join(", ")
let elemType = if node.typ.inner.len > 0: typeToC(be, node.typ.inner[0]) else: "void"
return &"({sliceName}){{.data = ({elemType}[]){{{elemsStr}}}, .len = {node.sliceInitLen}}}"
of hSliceIndex:
let base = be.emitExpr(node.sliceIndexBase)
let idx = be.emitExpr(node.sliceIndexIndex)
if node.sliceIndexBoundsCheck:
return &"(bux_bounds_check((size_t)({idx}), ({base}).len), ({base}).data[{idx}])"
else:
return &"({base}).data[{idx}]"
of hTupleInit:
var elems: seq[string] = @[]
for e in node.tupleInitElements:
elems.add(be.emitExpr(e))
return &"{{{elems.join(\", \")}}}"
of hCast:
let operand = be.emitExpr(node.castOperand)
let typ = typeToC(be, node.castType)
return &"(({typ}){operand})"
of hIs:
return "true" # TODO: proper type checking
of hSizeOf:
let typ = typeToC(be, node.sizeOfType)
return &"sizeof({typ})"
of hSpawn:
if node.spawnAsync:
# Async coroutine spawn
return &"bux_async_spawn({node.spawnCallee})"
else:
# OS thread spawn
var argsStr = "NULL"
if node.spawnArgs.len > 0:
argsStr = &"(void*){be.emitExpr(node.spawnArgs[0])}"
return &"bux_task_spawn((void* (*)(void*)){node.spawnCallee}, {argsStr})"
of hDynRef:
let data = be.emitExpr(node.dynRefData)
let iface = node.dynRefInterface
let concrete = node.dynRefConcreteType
return &"({iface}_FatPtr){{.data = {data}, .vtable = &{concrete}_{iface}_VTable}}"
of hDynCall:
let receiver = be.emitExpr(node.dynCallReceiver)
let methodName = node.dynCallMethod
var args: seq[string] = @[]
args.add(&"{receiver}.data")
for i in 1 ..< node.dynCallArgs.len:
args.add(be.emitExpr(node.dynCallArgs[i]))
let argsStr = args.join(", ")
return &"({receiver}.vtable->{methodName}({argsStr}))"
of hIf:
# Ternary expression
let cond = be.emitExpr(node.ifCond)
let thenE = be.emitExpr(node.ifThen)
let elseE = be.emitExpr(node.ifElse)
return &"({cond} ? {thenE} : {elseE})"
of hAssign:
let target = be.emitExpr(node.assignTarget)
let value = be.emitExpr(node.assignValue)
let op = operatorToC(node.assignOp)
return &"({target} {op} {value})"
of hBlock:
# For block expressions, just emit the last expression
if node.blockExpr != nil:
return be.emitExpr(node.blockExpr)
elif node.blockStmts.len > 0:
return be.emitExpr(node.blockStmts[^1])
return "0"
of hMatch:
return "0" # TODO: match expression lowering
else:
return "0"
proc emitStmt(be: var CBackend, node: HirNode) =
if node == nil: return
case node.kind
of hReturn:
if node.returnValue != nil:
let val = be.emitExpr(node.returnValue)
be.emitLine(&"return {val};")
else:
be.emitLine("return;")
of hIf:
let cond = be.emitExpr(node.ifCond)
be.emitLine(&"if ({cond}) {{")
inc be.indent
be.emitStmt(node.ifThen)
dec be.indent
if node.ifElse != nil:
be.emitLine("} else {")
inc be.indent
be.emitStmt(node.ifElse)
dec be.indent
be.emitLine("}")
of hWhile:
let cond = be.emitExpr(node.whileCond)
be.emitLine(&"while ({cond}) {{")
inc be.indent
be.emitStmt(node.whileBody)
dec be.indent
be.emitLine("}")
of hLoop:
be.emitLine("while (1) {")
inc be.indent
be.emitStmt(node.loopBody)
dec be.indent
be.emitLine("}")
of hBreak:
be.emitLine("break;")
of hContinue:
be.emitLine("continue;")
of hEmit:
be.emitLine(node.emitCode)
of hBlock:
if node.isScope:
be.emitLine("{")
inc be.indent
for stmt in node.blockStmts:
be.emitStmt(stmt)
if node.blockExpr != nil:
let val = be.emitExpr(node.blockExpr)
be.emitLine(&"{val};")
if node.isScope:
dec be.indent
be.emitLine("}")
of hAlloca:
let typ = typeToC(be, node.allocaType)
be.emitLine(&"{typ} {node.allocaName};")
of hStore:
let ptrExpr = be.emitExpr(node.storePtr)
let val = be.emitExpr(node.storeValue)
be.emitLine(&"{ptrExpr} = {val};")
of hAssign:
let target = be.emitExpr(node.assignTarget)
let value = be.emitExpr(node.assignValue)
let op = operatorToC(node.assignOp)
be.emitLine(&"{target} {op} {value};")
of hCall:
let expr = be.emitExpr(node)
be.emitLine(&"{expr};")
of hCallIndirect:
let expr = be.emitExpr(node)
be.emitLine(&"{expr};")
else:
# Expression statement
let expr = be.emitExpr(node)
be.emitLine(&"{expr};")
proc emitFunc*(be: var CBackend, hfunc: HirFunc) =
let retType = typeToC(be, hfunc.retType)
var params: seq[string] = @[]
for p in hfunc.params:
params.add(&"{typeToC(be, p.typ)} {p.name}")
if params.len == 0:
params.add("void")
let paramsStr = params.join(", ")
be.emitLine(&"{retType} {hfunc.name}({paramsStr}) {{")
inc be.indent
if hfunc.body != nil:
if hfunc.body.kind == hBlock:
for stmt in hfunc.body.blockStmts:
be.emitStmt(stmt)
if hfunc.body.blockExpr != nil and hfunc.retType.kind != tkVoid:
let val = be.emitExpr(hfunc.body.blockExpr)
be.emitLine(&"return {val};")
else:
be.emitStmt(hfunc.body)
dec be.indent
be.emitLine("}")
be.emitLine("")
proc emitStruct*(be: var CBackend, name: string, fields: seq[tuple[name: string, typ: Type]]) =
be.emitLine(&"typedef struct {name} {{")
inc be.indent
for f in fields:
let typ = typeToC(be, f.typ)
be.emitLine(&"{typ} {f.name};")
dec be.indent
be.emitLine(&"}} {name};")
be.emitLine("")
proc emitEnum*(be: var CBackend, name: string, variants: seq[HirEnumVariant]) =
# Check if this is a simple enum (no data) or algebraic enum (with data)
var hasData = false
for v in variants:
if v.fields.len > 0 or v.namedFields.len > 0:
hasData = true
break
if not hasData:
# Simple enum - generate as before
be.emitLine(&"typedef enum {{")
inc be.indent
for i, v in variants:
if i < variants.len - 1:
be.emitLine(&"{name}_{v.name},")
else:
be.emitLine(&"{name}_{v.name}")
dec be.indent
be.emitLine(&"}} {name};")
be.emitLine("")
else:
# Algebraic enum - generate tagged union
# 1. Generate tag enum
be.emitLine(&"typedef enum {{")
inc be.indent
for i, v in variants:
if i < variants.len - 1:
be.emitLine(&"{name}_{v.name},")
else:
be.emitLine(&"{name}_{v.name}")
dec be.indent
be.emitLine(&"}} {name}_Tag;")
be.emitLine("")
# 2. Generate union for data
be.emitLine(&"typedef union {{")
inc be.indent
for v in variants:
if v.fields.len > 0:
# Positional fields
for i, f in v.fields:
let typ = typeToC(be, f)
be.emitLine(&"{typ} {v.name}_{i};")
elif v.namedFields.len > 0:
# Named fields - generate as struct
be.emitLine(&"struct {{")
inc be.indent
for nf in v.namedFields:
let typ = typeToC(be, nf.typ)
be.emitLine(&"{typ} {nf.name};")
dec be.indent
be.emitLine(&"}} {v.name};")
dec be.indent
be.emitLine(&"}} {name}_Data;")
be.emitLine("")
# 3. Generate main struct with tag + union
be.emitLine(&"typedef struct {{")
inc be.indent
be.emitLine(&"{name}_Tag tag;")
be.emitLine(&"{name}_Data data;")
dec be.indent
be.emitLine(&"}} {name};")
be.emitLine("")
proc emitExternDecl*(be: var CBackend, efunc: HirFunc) =
let retType = typeToC(be, efunc.retType)
var params: seq[string] = @[]
for p in efunc.params:
params.add(&"{typeToC(be, p.typ)} {p.name}")
if params.len == 0:
params.add("void")
let paramsStr = params.join(", ")
be.emitLine(&"extern {retType} {efunc.name}({paramsStr});")
proc collectSliceTypes(module: HirModule): seq[tuple[name: string, elem: string]] =
## Pre-pass: collect all slice types used in the module.
var dummyBe = initCBackend()
for f in module.funcs:
discard typeToC(dummyBe, f.retType)
for p in f.params:
discard typeToC(dummyBe, p.typ)
for ef in module.externFuncs:
discard typeToC(dummyBe, ef.retType)
for p in ef.params:
discard typeToC(dummyBe, p.typ)
for s in module.structs:
for f in s.fields:
discard typeToC(dummyBe, f.typ)
for c in module.consts:
if c.value != nil:
discard typeToC(dummyBe, c.value.typ)
return dummyBe.sliceTypeDefs
proc emitModule*(be: var CBackend, module: HirModule): string =
# Header
be.emitLine("/* Generated by Bux Compiler */")
be.emitLine("#include <stdio.h>")
be.emitLine("#include <stdlib.h>")
be.emitLine("#include <stdint.h>")
be.emitLine("#include <stdbool.h>")
be.emitLine("#include <string.h>")
be.emitLine("")
# Pre-collect slice types so we can emit forward declarations early
let sliceTypes = collectSliceTypes(module)
# Forward declarations
for s in module.structs:
be.emitLine(&"typedef struct {s.name} {s.name};")
if module.structs.len > 0:
be.emitLine("")
# Forward declarations for trait object fat pointers
for iface in module.interfaces:
if not iface.hasAssocTypes:
be.emitLine(&"typedef struct {iface.name}_FatPtr {iface.name}_FatPtr;")
if module.interfaces.len > 0:
be.emitLine("")
# Extern function declarations
if module.externFuncs.len > 0:
be.emitLine("/* Extern function declarations */")
for ef in module.externFuncs:
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} \"{cEscape(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)
# Enum definitions
for e in module.enums:
be.emitEnum(e.name, e.variants)
# Slice fat-pointer typedefs
if sliceTypes.len > 0:
be.emitLine("/* Slice types */")
for st in sliceTypes:
be.emitLine(&"typedef struct {{ {st.elem}* data; size_t len; }} {st.name};")
be.emitLine("")
# Forward declarations for all functions
for f in module.funcs:
let retType = typeToC(be, f.retType)
var params: seq[string] = @[]
for p in f.params:
params.add(typeToC(be, p.typ) & " " & p.name)
if params.len == 0:
params.add("void")
be.emitLine(retType & " " & f.name & "(" & params.join(", ") & ");")
be.emitLine("")
# Trait object vtable and fat pointer struct definitions
for iface in module.interfaces:
if iface.hasAssocTypes:
continue # Skip vtables for interfaces with associated types (not yet supported)
let ifaceName = iface.name
# VTable struct
be.emitLine(&"typedef struct {ifaceName}_VTable {{")
inc be.indent
for m in iface.methods:
var paramCtypes: seq[string] = @[]
for i, p in m.params:
if i == 0:
paramCtypes.add("void* self") # First param is always self (erased)
else:
paramCtypes.add(typeToC(be, p) & " param")
if paramCtypes.len == 0:
paramCtypes.add("void")
let ret = typeToC(be, m.ret)
let paramsStr = paramCtypes.join(", ")
be.emitLine(&"{ret} (*{m.name})({paramsStr});")
dec be.indent
be.emitLine(&"}} {ifaceName}_VTable;")
# Fat pointer struct
be.emitLine(&"typedef struct {ifaceName}_FatPtr {{")
inc be.indent
be.emitLine("void* data;")
be.emitLine(&"{ifaceName}_VTable* vtable;")
dec be.indent
be.emitLine(&"}} {ifaceName}_FatPtr;")
be.emitLine("")
# VTable instances
for vt in module.vtables:
if vt.hasAssocTypes:
continue # Skip vtables for interfaces with associated types
let varName = vt.concreteType & "_" & vt.interfaceName & "_VTable"
be.emitLine(&"{vt.interfaceName}_VTable {varName} = {{")
inc be.indent
for m in vt.methodNames:
be.emitLine(&".{m} = (void*){vt.concreteType}_{m},")
dec be.indent
be.emitLine("};")
be.emitLine("")
# Function definitions
var hasMain = false
for f in module.funcs:
be.emitFunc(f)
if f.name == "Main":
hasMain = true
# Generate C main wrapper if Bux Main exists
if hasMain:
be.emitLine("/* C entry point wrapper */")
be.emitLine("extern int g_argc;")
be.emitLine("extern char** g_argv;")
be.emitLine("int main(int argc, char** argv) {")
be.emitLine(" g_argc = argc;")
be.emitLine(" g_argv = argv;")
be.emitLine(" return Main();")
be.emitLine("}")
be.emitLine("")
return be.output
+608
View File
@@ -0,0 +1,608 @@
import std/[os, strutils, terminal, strformat, osproc, sets]
import lexer, parser, ast, sema, manifest, hir, hir_lower, c_backend, types, scope
type
ColorMode* = enum
cmAuto
cmOn
cmOff
GlobalOptions* = object
color*: ColorMode
quiet*: bool
verbose*: bool
proc printUsage*() =
echo """Bux Programming Language (bootstrap compiler)
Usage: bux [options] <command> [command-options]
Commands:
new <name> Create a new Bux package
init Initialize a Bux package in the current directory
add <name> [ver] Add a dependency (--path, --git)
install Resolve and install dependencies
build Build the current package
run Build and run the current package
check Type-check the current package
clean Remove build artifacts
help Show this help message
version Show version
Global options:
--color <auto|on|off> Control colored output (default: auto)
-q, --quiet Suppress non-error output
-v, --verbose Verbose output
"""
proc parseGlobalOptions(args: seq[string]): tuple[opts: GlobalOptions, rest: seq[string], ok: bool] =
result.opts = GlobalOptions(color: cmAuto, quiet: false, verbose: false)
result.rest = @[]
result.ok = true
var i = 0
while i < args.len:
let arg = args[i]
if arg == "--color":
if i + 1 >= args.len:
stderr.writeLine("error: --color requires an argument")
result.ok = false
return
inc i
case args[i].toLowerAscii()
of "auto": result.opts.color = cmAuto
of "on": result.opts.color = cmOn
of "off": result.opts.color = cmOff
else:
stderr.writeLine(&"error: unknown --color value '{args[i]}'")
result.ok = false
return
elif arg == "-q" or arg == "--quiet":
result.opts.quiet = true
elif arg == "-v" or arg == "--verbose":
result.opts.verbose = true
else:
result.rest.add(arg)
inc i
proc shouldUseColor(opts: GlobalOptions): bool =
case opts.color
of cmOn: true
of cmOff: false
of cmAuto: terminal.isatty(stdout)
proc printError(msg: string, useColor: bool) =
if useColor:
stdout.setForegroundColor(fgRed)
stdout.write("error: ")
stdout.resetAttributes()
stdout.writeLine(msg)
else:
stderr.writeLine("error: " & msg)
proc printInfo(msg: string, useColor: bool) =
if useColor:
stdout.setForegroundColor(fgCyan)
stdout.write("info: ")
stdout.resetAttributes()
stdout.writeLine(msg)
else:
echo("info: " & msg)
# ---------------------------------------------------------------------------
# Commands
# ---------------------------------------------------------------------------
proc cmdNew*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
if args.len < 1:
printError("'new' requires a package name", useColor)
return 1
let name = args[0]
let root = getCurrentDir() / name
if dirExists(root):
printError(&"directory '{name}' already exists", useColor)
return 1
createDir(root / "src")
writeFile(root / "bux.toml", &"""[Package]
Name = "{name}"
Version = "0.1.0"
Type = "bin"
[Build]
Output = "Bin"
""")
writeFile(root / "src" / "Main.bux", """import Std::Io::PrintLine;
func Main() -> int {
PrintLine(c8"Hello, Bux!");
return 0;
}
""")
if not opts.quiet:
printInfo(&"Created Bux package '{name}'", useColor)
return 0
proc cmdInit*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
let root = getCurrentDir()
if fileExists(root / "bux.toml"):
printError("bux.toml already exists", useColor)
return 1
let name = splitPath(root).tail
writeFile(root / "bux.toml", &"""[Package]
Name = "{name}"
Version = "0.1.0"
Type = "bin"
[Build]
Output = "Bin"
""")
if not dirExists(root / "src"):
createDir(root / "src")
if not opts.quiet:
printInfo(&"Initialized Bux package '{name}'", useColor)
return 0
proc cmdAdd*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
let root = getCurrentDir()
let manifestPath = root / "bux.toml"
if not fileExists(manifestPath):
printError("no bux.toml found", useColor)
return 1
if args.len == 0:
printError("usage: bux add <name> [version] [--path <path>] [--git <url>]", useColor)
return 1
let depName = args[0]
var version = "*"
var path = ""
var gitUrl = ""
var i = 1
while i < args.len:
case args[i]
of "--path":
if i + 1 < args.len:
path = args[i + 1]
inc i
else:
printError("--path requires a value", useColor)
return 1
of "--git":
if i + 1 < args.len:
gitUrl = args[i + 1]
inc i
else:
printError("--git requires a value", useColor)
return 1
else:
version = args[i]
inc i
# Append to bux.toml
var depLine = ""
if path.len > 0:
depLine = &"{depName} = {{ Path = \"{path}\" }}"
elif gitUrl.len > 0:
depLine = &"{depName} = {{ Version = \"{version}\", Source = \"{gitUrl}\" }}"
else:
depLine = &"{depName} = \"{version}\""
var content = readFile(manifestPath)
# Ensure [Dependencies] section exists
if content.find("[Dependencies]") < 0:
content.add("\n[Dependencies]\n")
# Append dependency line
content.add(depLine & "\n")
writeFile(manifestPath, content)
if not opts.quiet:
printInfo(&"Added dependency '{depName}' to bux.toml", useColor)
return 0
proc cmdInstall*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
let root = getCurrentDir()
let manifestPath = root / "bux.toml"
if not fileExists(manifestPath):
printError("no bux.toml found", useColor)
return 1
let man = loadManifest(manifestPath)
var lock = Lockfile(entries: @[])
let cacheDir = getHomeDir() / ".bux" / "packages"
if not dirExists(cacheDir):
createDir(cacheDir)
# Resolve each dependency
for dep in man.dependencies:
case dep.kind
of dkPath:
let absPath = if dep.path.isAbsolute: dep.path else: root / dep.path
if not dirExists(absPath):
printError(&"path dependency not found: {absPath}", useColor)
return 1
# Read dependency manifest
let depManifestPath = absPath / "bux.toml"
if fileExists(depManifestPath):
let depMan = loadManifest(depManifestPath)
lock.entries.add(LockEntry(name: dep.name, version: depMan.version, source: absPath))
else:
lock.entries.add(LockEntry(name: dep.name, version: "0.0.0", source: absPath))
if not opts.quiet:
printInfo(&"Resolved path dependency '{dep.name}' from {absPath}", useColor)
of dkGit:
let depDir = cacheDir / dep.name
if not dirExists(depDir):
if not opts.quiet:
printInfo(&"Cloning '{dep.name}' from {dep.gitUrl}...", useColor)
let (outp, code) = execCmdEx(&"git clone {dep.gitUrl} {depDir} 2>&1")
if code != 0:
printError(&"failed to clone {dep.gitUrl}: {outp}", useColor)
return 1
else:
if not opts.quiet:
printInfo(&"Using cached '{dep.name}' from {depDir}", useColor)
lock.entries.add(LockEntry(name: dep.name, version: dep.gitVersion, source: dep.gitUrl))
of dkVersion:
# For version-based deps without a registry, we just record them
# TODO: lookup in registry
lock.entries.add(LockEntry(name: dep.name, version: dep.versionReq, source: "registry"))
if not opts.quiet:
printInfo(&"Recorded dependency '{dep.name}' = {dep.versionReq}", useColor)
# Save lockfile
let lockPath = root / "bux.lock"
saveLockfile(lockPath, lock)
if not opts.quiet:
printInfo(&"Generated {lockPath}", useColor)
return 0
proc collectStdlibDecls(stdlibDir: string): seq[Decl]
proc getDeclName(d: Decl): string
proc mergeDecls(stdlibDecls: seq[Decl], userDecls: seq[Decl]): seq[Decl]
proc collectDepDecls(lock: Lockfile, root: string, opts: GlobalOptions): seq[Decl]
proc cmdCheck*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
let root = if args.len > 0: absolutePath(args[0]) else: getCurrentDir()
let manifestPath = root / "bux.toml"
if not fileExists(manifestPath):
printError("no bux.toml found", useColor)
return 1
let man = loadManifest(manifestPath)
let srcDir = root / "src"
if not dirExists(srcDir):
printError("no src/ directory found", useColor)
return 1
# Phase 1: Parse all .bux files and collect declarations
var allModuleItems: seq[Decl] = @[]
var foundMain = false
for kind, path in walkDir(srcDir):
if kind == pcFile and path.endsWith(".bux"):
let source = readFile(path)
let lexRes = tokenize(source, path)
if lexRes.hasErrors:
printError(&"lex errors in {path}", useColor)
for d in lexRes.diagnostics:
echo $d
return 1
let parseRes = parse(lexRes.tokens, path)
if parseRes.diagnostics.len > 0:
printError(&"parse errors in {path}", useColor)
for d in parseRes.diagnostics:
echo &"error: {d.message} at {d.loc}"
return 1
# Flatten declarations from module wrappers
for decl in parseRes.module.items:
if decl.kind == dkModule:
for sub in decl.declModuleItems:
allModuleItems.add(sub)
else:
allModuleItems.add(decl)
if splitFile(path).name == "Main":
foundMain = true
if not foundMain:
printError("no Main.bux found in src/", useColor)
return 1
# Phase 2: Merge with stdlib and check
var stdlibDir = ""
let stdlibSearchPaths = @[
getAppDir() / ".." / "library",
getAppDir() / "library",
getCurrentDir() / "library",
"/home/ziko/z-git/bux/bux/library",
]
for path in stdlibSearchPaths:
if dirExists(path):
stdlibDir = path
break
let stdlibDecls = collectStdlibDecls(stdlibDir)
let lock = loadLockfile(root / "bux.lock")
let depDecls = collectDepDecls(lock, root, opts)
let stdlibAndDeps = mergeDecls(stdlibDecls, depDecls)
let mergedItems = mergeDecls(stdlibAndDeps, allModuleItems)
var unifiedModule = newModule("main")
unifiedModule.items = mergedItems
let semaRes = analyze(unifiedModule)
if semaRes.hasErrors:
printError("type errors in project", useColor)
for d in semaRes.diagnostics:
let sev = if d.severity == sdsError: "error" else: "warning"
echo &"{sev}: {d.message} at {d.loc}"
return 1
if not opts.quiet:
printInfo("check passed", useColor)
return 0
proc collectStdlibDecls(stdlibDir: string): seq[Decl] =
result = @[]
if not dirExists(stdlibDir): return
for path in walkDirRec(stdlibDir):
if path.endsWith(".bux"):
let source = readFile(path)
let lexRes = tokenize(source, path)
if lexRes.hasErrors: continue
let parseRes = parse(lexRes.tokens, path)
if parseRes.diagnostics.len > 0: continue
for item in parseRes.module.items:
if item.kind == dkModule:
for sub in item.declModuleItems:
result.add(sub)
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 collectDepDecls(lock: Lockfile, root: string, opts: GlobalOptions): seq[Decl] =
## Collect declarations from all locked dependencies.
let cacheDir = getHomeDir() / ".bux" / "packages"
let useColor = shouldUseColor(opts)
for entry in lock.entries:
var depSrcDir = ""
if dirExists(entry.source):
# Path-based dependency
depSrcDir = entry.source / "src"
elif entry.source.startsWith("http") or entry.source.startsWith("git@"):
# Git-based dependency in cache
depSrcDir = cacheDir / entry.name / "src"
if depSrcDir == "" or not dirExists(depSrcDir):
continue
for kind, path in walkDir(depSrcDir):
if kind == pcFile and path.endsWith(".bux"):
let source = readFile(path)
let lexRes = tokenize(source, path)
if lexRes.hasErrors:
continue
let parseRes = parse(lexRes.tokens, path)
if parseRes.diagnostics.len > 0:
continue
for decl in parseRes.module.items:
if decl.kind == dkModule:
for sub in decl.declModuleItems:
result.add(sub)
else:
result.add(decl)
if not opts.quiet:
printInfo(&"Loaded dependency '{entry.name}' from {depSrcDir}", useColor)
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 = if args.len > 0: absolutePath(args[0]) else: getCurrentDir()
let manifestPath = root / "bux.toml"
if not fileExists(manifestPath):
printError("no bux.toml found", useColor)
return 1
let man = loadManifest(manifestPath)
let srcDir = root / "src"
if not dirExists(srcDir):
printError("no src/ directory found", useColor)
return 1
# Create build directory
let buildDir = root / "build"
if not dirExists(buildDir):
createDir(buildDir)
# Find stdlib directory
var stdlibDir = ""
let stdlibSearchPaths = @[
getAppDir() / ".." / "library",
getAppDir() / "library",
root / "library",
"/home/ziko/z-git/bux/bux/library",
]
for path in stdlibSearchPaths:
if dirExists(path):
stdlibDir = path
break
# Collect stdlib declarations once
let stdlibDecls = collectStdlibDecls(stdlibDir)
# Collect dependency declarations from lockfile
let lock = loadLockfile(root / "bux.lock")
let depDecls = collectDepDecls(lock, root, opts)
# Phase 1: Parse all .bux files and collect declarations
var allModuleItems: seq[Decl] = @[]
var foundMain = false
for kind, path in walkDir(srcDir):
if kind == pcFile and path.endsWith(".bux"):
let source = readFile(path)
let lexRes = tokenize(source, path)
if lexRes.hasErrors:
printError(&"lex errors in {path}", useColor)
for d in lexRes.diagnostics:
echo $d
return 1
let parseRes = parse(lexRes.tokens, path)
if parseRes.diagnostics.len > 0:
printError(&"parse errors in {path}", useColor)
for d in parseRes.diagnostics:
echo &"error: {d.message} at {d.loc}"
return 1
# Flatten: extract declarations from module wrappers
for decl in parseRes.module.items:
if decl.kind == dkModule:
for sub in decl.declModuleItems:
allModuleItems.add(sub)
else:
allModuleItems.add(decl)
if splitFile(path).name == "Main":
foundMain = true
if not foundMain:
printError("no Main.bux found in src/", useColor)
return 1
# Phase 2: Merge stdlib + deps + project (later shadow earlier)
let stdlibAndDeps = mergeDecls(stdlibDecls, depDecls)
let mergedItems = mergeDecls(stdlibAndDeps, 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:
printError("type errors in project", useColor)
for d in semaRes.diagnostics:
let sev = if d.severity == sdsError: "error" else: "warning"
echo &"{sev}: {d.message} at {d.loc}"
return 1
let hirMod = lowerModule(unifiedModule, semaCtx)
var cbe = initCBackend()
var allCCode = cbe.emitModule(hirMod)
# Write C file
let cFile = buildDir / "main.c"
writeFile(cFile, allCCode)
# Copy runtime and stdlib files
let runtimeDst = buildDir / "runtime.c"
let ioDst = buildDir / "io.c"
if stdlibDir == "":
printError("stdlib directory not found", useColor)
return 1
# Copy runtime.c
let runtimeSrc = stdlibDir / "runtime" / "runtime.c"
if fileExists(runtimeSrc):
copyFile(runtimeSrc, runtimeDst)
else:
printError("runtime.c not found in library/runtime/", useColor)
return 1
# Copy io.c
let ioSrc = stdlibDir / "runtime" / "io.c"
if fileExists(ioSrc):
copyFile(ioSrc, ioDst)
else:
printError("io.c not found in library/runtime/", useColor)
return 1
# Compile with cc
let outputName = if man.name != "": man.name else: "bux_out"
let outputFile = buildDir / outputName
let ccCmd = &"cc -O2 -pthread -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm 2>&1"
if opts.verbose:
printInfo(&"running: {ccCmd}", useColor)
let (output, exitCode) = execCmdEx(ccCmd)
if exitCode != 0:
printError("C compilation failed:", useColor)
echo output
return 1
if not opts.quiet:
printInfo(&"build: {outputFile}", useColor)
return 0
proc cmdRun*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
let buildRes = cmdBuild(args, opts)
if buildRes != 0:
return buildRes
let root = if args.len > 0: absolutePath(args[0]) else: getCurrentDir()
let man = loadManifest(root / "bux.toml")
let outputName = if man.name != "": man.name else: "bux_out"
let outputFile = root / "build" / outputName
if not fileExists(outputFile):
printError("executable not found after build", useColor)
return 1
let exitCode = execCmd(outputFile)
return exitCode
proc cmdClean*(args: seq[string], opts: GlobalOptions): int =
let useColor = shouldUseColor(opts)
let root = getCurrentDir()
let buildDir = root / "build"
if dirExists(buildDir):
removeDir(buildDir)
if not opts.quiet:
printInfo("clean: build directory removed", useColor)
return 0
proc cmdVersion*(args: seq[string], opts: GlobalOptions): int =
echo "bux 0.1.0 (bootstrap)"
return 0
proc runCli*(args: seq[string]): int =
let (opts, rest, ok) = parseGlobalOptions(args)
if not ok:
return 1
if rest.len == 0:
printUsage()
return 0
let cmd = rest[0]
let cmdArgs = if rest.len > 1: rest[1..^1] else: @[]
case cmd
of "new": return cmdNew(cmdArgs, opts)
of "init": return cmdInit(cmdArgs, opts)
of "add": return cmdAdd(cmdArgs, opts)
of "install": return cmdInstall(cmdArgs, opts)
of "build": return cmdBuild(cmdArgs, opts)
of "run": return cmdRun(cmdArgs, opts)
of "check": return cmdCheck(cmdArgs, opts)
of "clean": return cmdClean(cmdArgs, opts)
of "version", "--version", "-v": return cmdVersion(cmdArgs, opts)
of "help", "--help", "-h":
printUsage()
return 0
else:
let useColor = shouldUseColor(opts)
printError(&"unknown command '{cmd}'", useColor)
return 1
+224
View File
@@ -0,0 +1,224 @@
import ast, types, token, source_location
type
HirKind* = enum
# Literals
hLit
hVar
hSelf
# Operations
hUnary
hBinary
hAssign
# Control flow
hIf
hWhile
hLoop
hBreak
hContinue
hReturn
# Memory
hAlloca
hLoad
hStore
hFieldPtr
hArrowField
hIndexPtr
hSliceIndex
# Functions
hCall
hCallIndirect
# Type operations
hCast
hIs
hSizeOf
# Concurrency
hSpawn
# Compile-time code generation
hEmit
# Trait objects (dynamic dispatch)
hDynRef
hDynCall
# Composite
hBlock
hStructInit
hSliceInit
hRange
hTupleInit
# Match (desugared to switch/branch later)
hMatch
HirNode* = ref object
loc*: SourceLocation
typ*: Type
case kind*: HirKind
of hLit:
litToken*: Token
of hVar:
varName*: string
of hSelf:
discard
of hUnary:
unaryOp*: TokenKind
unaryOperand*: HirNode
of hBinary:
binaryOp*: TokenKind
binaryLeft*: HirNode
binaryRight*: HirNode
of hAssign:
assignOp*: TokenKind
assignTarget*: HirNode
assignValue*: HirNode
of hIf:
ifCond*: HirNode
ifThen*: HirNode
ifElse*: HirNode
of hWhile:
whileCond*: HirNode
whileBody*: HirNode
of hLoop:
loopBody*: HirNode
of hBreak:
breakLabel*: string
of hContinue:
continueLabel*: string
of hReturn:
returnValue*: HirNode
of hAlloca:
allocaType*: Type
allocaName*: string
of hLoad:
loadPtr*: HirNode
of hStore:
storePtr*: HirNode
storeValue*: HirNode
of hFieldPtr:
fieldPtrBase*: HirNode
fieldName*: string
of hArrowField:
arrowFieldBase*: HirNode
arrowFieldName*: string
of hIndexPtr:
indexPtrBase*: HirNode
indexPtrIndex*: HirNode
of hCall:
callCallee*: string
callArgs*: seq[HirNode]
of hCallIndirect:
callIndirectCallee*: HirNode
callIndirectArgs*: seq[HirNode]
of hCast:
castOperand*: HirNode
castType*: Type
of hIs:
isOperand*: HirNode
isType*: Type
of hSizeOf:
sizeOfType*: Type
of hSpawn:
spawnCallee*: string
spawnArgs*: seq[HirNode]
spawnAsync*: bool
of hEmit:
emitCode*: string
of hDynRef:
dynRefData*: HirNode
dynRefInterface*: string
dynRefConcreteType*: string
of hDynCall:
dynCallReceiver*: HirNode
dynCallMethod*: string
dynCallArgs*: seq[HirNode]
of hBlock:
blockStmts*: seq[HirNode]
blockExpr*: HirNode
isScope*: bool
of hStructInit:
structInitName*: string
structInitFields*: seq[tuple[name: string, value: HirNode]]
of hSliceInit:
sliceInitElements*: seq[HirNode]
sliceInitLen*: int
of hSliceIndex:
sliceIndexBase*: HirNode
sliceIndexIndex*: HirNode
sliceIndexBoundsCheck*: bool
of hRange:
rangeLo*: HirNode
rangeHi*: HirNode
rangeInclusive*: bool
of hTupleInit:
tupleInitElements*: seq[HirNode]
of hMatch:
matchSubject*: HirNode
matchArms*: seq[HirMatchArm]
HirMatchArm* = object
pattern*: Pattern
body*: HirNode
HirFunc* = object
name*: string
params*: seq[tuple[name: string, typ: Type]]
retType*: Type
body*: HirNode
isPublic*: bool
HirEnumVariant* = object
name*: string
fields*: seq[Type] # Positional fields for algebraic enums
namedFields*: seq[tuple[name: string, typ: Type]] # Named fields
HirModule* = object
funcs*: seq[HirFunc]
externFuncs*: seq[HirFunc] # Functions declared with extern (no body)
structs*: seq[tuple[name: string, fields: seq[tuple[name: string, typ: Type]]]]
enums*: seq[tuple[name: string, variants: seq[HirEnumVariant]]]
consts*: seq[tuple[name: string, typ: Type, value: HirNode]]
interfaces*: seq[tuple[name: string, hasAssocTypes: bool, methods: seq[tuple[name: string, params: seq[Type], ret: Type]]]]
vtables*: seq[tuple[interfaceName: string, concreteType: string, methodNames: seq[string], hasAssocTypes: bool]]
# Constructor helpers
proc hirLit*(tok: Token, typ: Type, loc: SourceLocation): HirNode =
HirNode(kind: hLit, litToken: tok, typ: typ, loc: loc)
proc hirVar*(name: string, typ: Type, loc: SourceLocation): HirNode =
HirNode(kind: hVar, varName: name, typ: typ, loc: loc)
proc hirSelf*(typ: Type, loc: SourceLocation): HirNode =
HirNode(kind: hSelf, typ: typ, loc: loc)
proc hirUnary*(op: TokenKind, operand: HirNode, typ: Type, loc: SourceLocation): HirNode =
HirNode(kind: hUnary, unaryOp: op, unaryOperand: operand, typ: typ, loc: loc)
proc hirBinary*(op: TokenKind, left, right: HirNode, typ: Type, loc: SourceLocation): HirNode =
HirNode(kind: hBinary, binaryOp: op, binaryLeft: left, binaryRight: right, typ: typ, loc: loc)
proc hirCall*(callee: string, args: seq[HirNode], typ: Type, loc: SourceLocation): HirNode =
HirNode(kind: hCall, callCallee: callee, callArgs: args, typ: typ, loc: loc)
proc hirReturn*(value: HirNode, loc: SourceLocation): HirNode =
HirNode(kind: hReturn, returnValue: value, typ: makeVoid(), loc: loc)
proc hirBlock*(stmts: seq[HirNode], expr: HirNode, typ: Type, loc: SourceLocation, isScope: bool = false): HirNode =
HirNode(kind: hBlock, blockStmts: stmts, blockExpr: expr, typ: typ, loc: loc, isScope: isScope)
proc hirAlloca*(name: string, typ: Type, loc: SourceLocation): HirNode =
HirNode(kind: hAlloca, allocaType: typ, allocaName: name, typ: makePointer(typ), loc: loc)
proc hirStore*(ptrNode, value: HirNode, loc: SourceLocation): HirNode =
HirNode(kind: hStore, storePtr: ptrNode, storeValue: value, typ: makeVoid(), loc: loc)
proc hirLoad*(ptrNode: HirNode, typ: Type, loc: SourceLocation): HirNode =
HirNode(kind: hLoad, loadPtr: ptrNode, typ: typ, loc: loc)
proc hirEmit*(code: string, loc: SourceLocation): HirNode =
HirNode(kind: hEmit, emitCode: code, typ: makeVoid(), loc: loc)
proc hirDynRef*(data: HirNode, interfaceName, concreteType: string, loc: SourceLocation): HirNode =
HirNode(kind: hDynRef, dynRefData: data, dynRefInterface: interfaceName,
dynRefConcreteType: concreteType, typ: makeDynRef(interfaceName), loc: loc)
proc hirDynCall*(receiver: HirNode, methodName: string, args: seq[HirNode], typ: Type, loc: SourceLocation): HirNode =
HirNode(kind: hDynCall, dynCallReceiver: receiver, dynCallMethod: methodName,
dynCallArgs: args, typ: typ, loc: loc)
File diff suppressed because it is too large Load Diff
+619
View File
@@ -0,0 +1,619 @@
import std/[strutils, strformat]
import token, source_location
type
LexerDiagnosticSeverity* = enum
ldsWarning
ldsError
LexerDiagnostic* = object
severity*: LexerDiagnosticSeverity
loc*: SourceLocation
message*: string
LexerResult* = object
tokens*: seq[Token]
diagnostics*: seq[LexerDiagnostic]
proc hasErrors*(res: LexerResult): bool =
for d in res.diagnostics:
if d.severity == ldsError:
return true
return false
proc `$`*(d: LexerDiagnostic): string =
let sev = if d.severity == ldsError: "error" else: "warning"
result = &"{sev}: {d.message} at {d.loc}"
type
Lexer* = object
source: string
sourceName: string
pos: int
line: uint32
col: uint32
tokens: seq[Token]
diagnostics: seq[LexerDiagnostic]
proc initLexer*(source, sourceName: string): Lexer =
result.source = source
result.sourceName = sourceName
result.pos = 0
result.line = 1
result.col = 1
proc isAtEnd(lex: Lexer): bool =
lex.pos >= lex.source.len
proc peek(lex: Lexer, ahead: int = 0): char =
let i = lex.pos + ahead
if i < lex.source.len:
return lex.source[i]
return '\0'
proc advance(lex: var Lexer): char =
result = lex.peek()
if not lex.isAtEnd():
inc lex.pos
if result == '\n':
inc lex.line
lex.col = 1
else:
inc lex.col
proc match(lex: var Lexer, expected: char): bool =
if lex.isAtEnd(): return false
if lex.peek() != expected: return false
discard lex.advance()
return true
proc matchStr(lex: var Lexer, s: string): bool =
for i, c in s:
if lex.peek(i) != c:
return false
for _ in s:
discard lex.advance()
return true
proc currentLocation(lex: Lexer): SourceLocation =
result = SourceLocation(line: lex.line, column: lex.col, offset: uint32(lex.pos))
proc emitError(lex: var Lexer, loc: SourceLocation, message: string) =
lex.diagnostics.add(LexerDiagnostic(severity: ldsError, loc: loc, message: message))
proc emitWarning(lex: var Lexer, loc: SourceLocation, message: string) =
lex.diagnostics.add(LexerDiagnostic(severity: ldsWarning, loc: loc, message: message))
proc makeToken(lex: Lexer, kind: TokenKind, startLoc: SourceLocation, startPos: int): Token =
let text = lex.source[startPos ..< lex.pos]
result = Token(kind: kind, text: text, loc: startLoc)
# ---------------------------------------------------------------------------
# Whitespace / comments
# ---------------------------------------------------------------------------
proc skipLineComment(lex: var Lexer) =
while not lex.isAtEnd() and lex.peek() != '\n':
discard lex.advance()
proc skipBlockComment(lex: var Lexer) =
let startLoc = lex.currentLocation()
var depth = 1
while not lex.isAtEnd() and depth > 0:
if lex.peek() == '/' and lex.peek(1) == '*':
discard lex.advance()
discard lex.advance()
inc depth
elif lex.peek() == '*' and lex.peek(1) == '/':
discard lex.advance()
discard lex.advance()
dec depth
else:
discard lex.advance()
if depth > 0:
lex.emitError(startLoc, "unterminated block comment")
proc skipWhitespace(lex: var Lexer) =
while not lex.isAtEnd():
let c = lex.peek()
if c in {' ', '\t', '\r'}:
discard lex.advance()
elif c == '/' and lex.peek(1) == '/':
lex.skipLineComment()
elif c == '/' and lex.peek(1) == '*':
discard lex.advance()
discard lex.advance()
lex.skipBlockComment()
else:
break
# ---------------------------------------------------------------------------
# Identifiers
# ---------------------------------------------------------------------------
proc isIdentStart(c: char): bool =
c in {'a'..'z', 'A'..'Z', '_'}
proc isIdentChar(c: char): bool =
c in {'a'..'z', 'A'..'Z', '0'..'9', '_'}
proc scanIdent(lex: var Lexer, startLoc: SourceLocation): Token =
let startPos = lex.pos
while not lex.isAtEnd() and isIdentChar(lex.peek()):
discard lex.advance()
let text = lex.source[startPos ..< lex.pos]
var kind = keywordKind(text)
if text == "_":
kind = tkUnderscore
result = Token(kind: kind, text: text, loc: startLoc)
# ---------------------------------------------------------------------------
# Numbers
# ---------------------------------------------------------------------------
proc scanIntSuffix(lex: var Lexer) =
# i8, i16, i32, i64, u8, u16, u32, u64, f32, f64
if lex.isAtEnd(): return
let c = lex.peek()
if c in {'i', 'u', 'f'}:
discard lex.advance()
while not lex.isAtEnd() and lex.peek() in {'0'..'9'}:
discard lex.advance()
proc scanHexDigits(lex: var Lexer) =
while not lex.isAtEnd() and lex.peek() in {'0'..'9', 'a'..'f', 'A'..'F'}:
discard lex.advance()
proc scanBinDigits(lex: var Lexer) =
while not lex.isAtEnd() and lex.peek() in {'0', '1'}:
discard lex.advance()
proc scanOctDigits(lex: var Lexer) =
while not lex.isAtEnd() and lex.peek() in {'0'..'7'}:
discard lex.advance()
proc scanDecDigits(lex: var Lexer) =
while not lex.isAtEnd() and lex.peek() in {'0'..'9'}:
discard lex.advance()
proc scanNumber(lex: var Lexer, startLoc: SourceLocation): Token =
let startPos = lex.pos
var isFloat = false
if lex.peek() == '0' and lex.peek(1) in {'x', 'X', 'b', 'B', 'o', 'O'}:
discard lex.advance() # '0'
let prefix = lex.advance()
case prefix
of 'x', 'X': lex.scanHexDigits()
of 'b', 'B': lex.scanBinDigits()
of 'o', 'O': lex.scanOctDigits()
else: discard
lex.scanIntSuffix()
return lex.makeToken(tkIntLiteral, startLoc, startPos)
lex.scanDecDigits()
# Fractional part
if lex.peek() == '.' and lex.peek(1) in {'0'..'9'}:
isFloat = true
discard lex.advance() # '.'
lex.scanDecDigits()
# Exponent
if lex.peek() in {'e', 'E'}:
isFloat = true
discard lex.advance()
if lex.peek() in {'+', '-'}:
discard lex.advance()
if lex.peek() notin {'0'..'9'}:
lex.emitError(lex.currentLocation(), "expected digits in exponent")
else:
lex.scanDecDigits()
if isFloat:
# Optional f32/f64 suffix
if lex.peek() == 'f' and lex.peek(1) in {'3', '6'}:
discard lex.advance()
discard lex.advance()
result = lex.makeToken(tkFloatLiteral, startLoc, startPos)
else:
lex.scanIntSuffix()
result = lex.makeToken(tkIntLiteral, startLoc, startPos)
# ---------------------------------------------------------------------------
# Strings and chars
# ---------------------------------------------------------------------------
proc scanEscapeSequence(lex: var Lexer): string =
if lex.isAtEnd():
return ""
let c = lex.advance()
case c
of '\\': result = "\\"
of '"': result = "\""
of '\'': result = "\'"
of 'n': result = "\n"
of 'r': result = "\r"
of 't': result = "\t"
of '0': result = "\0"
of 'x':
var hexVal = ""
for _ in 0..<2:
if lex.isAtEnd() or lex.peek() notin {'0'..'9', 'a'..'f', 'A'..'F'}:
lex.emitError(lex.currentLocation(), "expected two hex digits after \\x")
break
hexVal.add(lex.advance())
if hexVal.len == 2:
try:
let code = parseHexInt(hexVal)
result = $chr(code)
except ValueError:
lex.emitError(lex.currentLocation(), &"invalid hex escape \\x{hexVal}")
result = ""
else:
result = ""
else:
lex.emitError(lex.currentLocation(), &"unknown escape sequence \\\\{c}")
result = $c
proc scanString(lex: var Lexer, startLoc: SourceLocation, prefixLen: int): Token =
let startPos = lex.pos - prefixLen
# prefixLen characters before the opening quote were already consumed by caller
# but we need to handle the quote itself
# Collect resolved string content to properly handle escape sequences
var resolved = ""
if lex.peek() == '"':
discard lex.advance()
while not lex.isAtEnd() and lex.peek() != '"':
if lex.peek() == '\n':
lex.emitError(lex.currentLocation(), "unterminated string literal")
break
if lex.peek() == '\\':
discard lex.advance()
resolved.add(lex.scanEscapeSequence())
else:
resolved.add(lex.advance())
if lex.isAtEnd():
lex.emitError(startLoc, "unterminated string literal")
else:
discard lex.advance() # closing "
# Rebuild text with resolved escapes: prefix + " + resolved + "
var text = lex.source[startPos ..< startPos + prefixLen]
text.add('"')
text.add(resolved)
text.add('"')
result = Token(kind: tkStringLiteral, text: text, loc: startLoc)
proc scanBacktickString(lex: var Lexer, startLoc: SourceLocation): Token =
## Scan a backtick-delimited raw string literal: content is literal,
## no escape processing, newlines are preserved.
let startPos = lex.pos - 1 # include the opening backtick
while not lex.isAtEnd() and lex.peek() != '`':
discard lex.advance()
if lex.isAtEnd():
lex.emitError(startLoc, "unterminated backtick string literal")
else:
discard lex.advance() # closing backtick
result = lex.makeToken(tkStringLiteral, startLoc, startPos)
proc scanChar(lex: var Lexer, startLoc: SourceLocation, prefixLen: int): Token =
let startPos = lex.pos - prefixLen
# Collect resolved char content to properly handle escape sequences
var resolved = ""
if lex.peek() == '\'':
discard lex.advance()
if lex.isAtEnd():
lex.emitError(startLoc, "unterminated char literal")
elif lex.peek() == '\n':
lex.emitError(lex.currentLocation(), "newline in char literal")
elif lex.peek() == '\\':
discard lex.advance()
resolved.add(lex.scanEscapeSequence())
else:
resolved.add(lex.advance())
if lex.isAtEnd() or lex.peek() != '\'':
lex.emitError(lex.currentLocation(), "expected closing ' for char literal")
else:
discard lex.advance()
# Rebuild text with resolved escape: prefix + ' + resolved + '
var text = lex.source[startPos ..< startPos + prefixLen]
text.add('\'')
text.add(resolved)
text.add('\'')
result = Token(kind: tkCharLiteral, text: text, loc: startLoc)
# ---------------------------------------------------------------------------
# Symbols / operators
# ---------------------------------------------------------------------------
proc scanSymbol(lex: var Lexer, startLoc: SourceLocation): Token =
let startPos = lex.pos
let c1 = lex.advance()
template check2(c2: char, kind2: TokenKind, kind1: TokenKind) =
if lex.peek() == c2:
discard lex.advance()
return lex.makeToken(kind2, startLoc, startPos)
else:
return lex.makeToken(kind1, startLoc, startPos)
template check3(c2: char, kind2: TokenKind, c3: char, kind3: TokenKind, kind1: TokenKind) =
if lex.peek() == c2:
discard lex.advance()
if lex.peek() == c3:
discard lex.advance()
return lex.makeToken(kind3, startLoc, startPos)
return lex.makeToken(kind2, startLoc, startPos)
else:
return lex.makeToken(kind1, startLoc, startPos)
template checkEq(c2: char, kind2: TokenKind, kind1: TokenKind) =
check2(c2, kind2, kind1)
case c1
of '(': return lex.makeToken(tkLParen, startLoc, startPos)
of ')': return lex.makeToken(tkRParen, startLoc, startPos)
of '{': return lex.makeToken(tkLBrace, startLoc, startPos)
of '}': return lex.makeToken(tkRBrace, startLoc, startPos)
of '[': return lex.makeToken(tkLBracket, startLoc, startPos)
of ']': return lex.makeToken(tkRBracket, startLoc, startPos)
of ',': return lex.makeToken(tkComma, startLoc, startPos)
of ';': return lex.makeToken(tkSemicolon, startLoc, startPos)
of '@': return lex.makeToken(tkAt, startLoc, startPos)
of '?': return lex.makeToken(tkQuestion, startLoc, startPos)
of '~': return lex.makeToken(tkTilde, startLoc, startPos)
of ':': check2(':', tkColonColon, tkColon)
of '.':
if lex.peek() == '.' and lex.peek(1) == '.':
discard lex.advance()
discard lex.advance()
return lex.makeToken(tkDotDotDot, startLoc, startPos)
elif lex.peek() == '.' and lex.peek(1) == '=':
discard lex.advance()
discard lex.advance()
return lex.makeToken(tkDotDotEqual, startLoc, startPos)
elif lex.peek() == '.':
discard lex.advance()
return lex.makeToken(tkDotDot, startLoc, startPos)
else:
return lex.makeToken(tkDot, startLoc, startPos)
of '-':
if lex.peek() == '>':
discard lex.advance()
return lex.makeToken(tkArrow, startLoc, startPos)
elif lex.peek() == '-':
discard lex.advance()
return lex.makeToken(tkMinusMinus, startLoc, startPos)
elif lex.peek() == '=':
discard lex.advance()
return lex.makeToken(tkMinusAssign, startLoc, startPos)
else:
return lex.makeToken(tkMinus, startLoc, startPos)
of '+':
if lex.peek() == '+':
discard lex.advance()
return lex.makeToken(tkPlusPlus, startLoc, startPos)
elif lex.peek() == '=':
discard lex.advance()
return lex.makeToken(tkPlusAssign, startLoc, startPos)
else:
return lex.makeToken(tkPlus, startLoc, startPos)
of '*':
if lex.peek() == '*':
discard lex.advance()
return lex.makeToken(tkStarStar, startLoc, startPos)
elif lex.peek() == '=':
discard lex.advance()
return lex.makeToken(tkStarAssign, startLoc, startPos)
else:
return lex.makeToken(tkStar, startLoc, startPos)
of '/':
if lex.peek() == '=':
discard lex.advance()
return lex.makeToken(tkSlashAssign, startLoc, startPos)
else:
return lex.makeToken(tkSlash, startLoc, startPos)
of '%':
if lex.peek() == '=':
discard lex.advance()
return lex.makeToken(tkPercentAssign, startLoc, startPos)
else:
return lex.makeToken(tkPercent, startLoc, startPos)
of '=':
if lex.peek() == '=':
discard lex.advance()
return lex.makeToken(tkEq, startLoc, startPos)
elif lex.peek() == '>':
discard lex.advance()
return lex.makeToken(tkFatArrow, startLoc, startPos)
else:
return lex.makeToken(tkAssign, startLoc, startPos)
of '!':
if lex.peek() == '=':
discard lex.advance()
return lex.makeToken(tkNe, startLoc, startPos)
else:
return lex.makeToken(tkBang, startLoc, startPos)
of '<':
if lex.peek() == '=':
discard lex.advance()
return lex.makeToken(tkLe, startLoc, startPos)
elif lex.peek() == '<':
discard lex.advance()
if lex.peek() == '=':
discard lex.advance()
return lex.makeToken(tkShlAssign, startLoc, startPos)
return lex.makeToken(tkShl, startLoc, startPos)
else:
return lex.makeToken(tkLt, startLoc, startPos)
of '>':
if lex.peek() == '=':
discard lex.advance()
return lex.makeToken(tkGe, startLoc, startPos)
elif lex.peek() == '>':
discard lex.advance()
if lex.peek() == '=':
discard lex.advance()
return lex.makeToken(tkShrAssign, startLoc, startPos)
return lex.makeToken(tkShr, startLoc, startPos)
else:
return lex.makeToken(tkGt, startLoc, startPos)
of '&':
if lex.peek() == '&':
discard lex.advance()
return lex.makeToken(tkAmpAmp, startLoc, startPos)
elif lex.peek() == '=':
discard lex.advance()
return lex.makeToken(tkAmpAssign, startLoc, startPos)
else:
return lex.makeToken(tkAmp, startLoc, startPos)
of '|':
if lex.peek() == '|':
discard lex.advance()
return lex.makeToken(tkPipePipe, startLoc, startPos)
elif lex.peek() == '=':
discard lex.advance()
return lex.makeToken(tkPipeAssign, startLoc, startPos)
else:
return lex.makeToken(tkPipe, startLoc, startPos)
of '^':
if lex.peek() == '=':
discard lex.advance()
return lex.makeToken(tkCaretAssign, startLoc, startPos)
else:
return lex.makeToken(tkCaret, startLoc, startPos)
of '#':
# Check for intrinsics: #line, #column, #file, #function, #date, #time, #module
let afterHash = lex.peek()
if afterHash == 'l' and lex.matchStr("line"):
return lex.makeToken(tkHashLine, startLoc, startPos)
elif afterHash == 'c' and lex.matchStr("column"):
return lex.makeToken(tkHashColumn, startLoc, startPos)
elif afterHash == 'f' and lex.peek(1) == 'i' and lex.peek(2) == 'l' and lex.peek(3) == 'e':
discard lex.matchStr("file")
return lex.makeToken(tkHashFile, startLoc, startPos)
elif afterHash == 'f' and lex.peek(1) == 'u' and lex.peek(2) == 'n':
discard lex.matchStr("function")
return lex.makeToken(tkHashFunction, startLoc, startPos)
elif afterHash == 'd' and lex.matchStr("date"):
return lex.makeToken(tkHashDate, startLoc, startPos)
elif afterHash == 't' and lex.matchStr("time"):
return lex.makeToken(tkHashTime, startLoc, startPos)
elif afterHash == 'm' and lex.matchStr("module"):
return lex.makeToken(tkHashModule, startLoc, startPos)
elif afterHash == 'e' and lex.matchStr("emit"):
return lex.makeToken(tkHashEmit, startLoc, startPos)
else:
return lex.makeToken(tkHash, startLoc, startPos)
else:
lex.emitError(startLoc, &"unexpected character '{c1}'")
return lex.makeToken(tkUnknown, startLoc, startPos)
# ---------------------------------------------------------------------------
# Main scanning loop
# ---------------------------------------------------------------------------
proc nextToken(lex: var Lexer): Token =
lex.skipWhitespace()
let startLoc = lex.currentLocation()
let startPos = lex.pos
if lex.isAtEnd():
return Token(kind: tkEndOfFile, text: "", loc: startLoc)
let c = lex.peek()
if c == '\n':
discard lex.advance()
return Token(kind: tkNewLine, text: "\n", loc: startLoc)
# String prefixes: c8" c16" c32" — must come before ident check
if c == 'c' and lex.peek(1) in {'8', '1', '3'}:
let d = lex.peek(1)
if d == '8' and lex.peek(2) == '"':
discard lex.advance() # c
discard lex.advance() # 8
return lex.scanString(startLoc, 2)
elif d == '1' and lex.peek(2) == '6' and lex.peek(3) == '"':
discard lex.advance()
discard lex.advance()
discard lex.advance()
return lex.scanString(startLoc, 3)
elif d == '3' and lex.peek(2) == '2' and lex.peek(3) == '"':
discard lex.advance()
discard lex.advance()
discard lex.advance()
return lex.scanString(startLoc, 3)
if c == '"':
return lex.scanString(startLoc, 0)
# Backtick-delimited raw string: `...`
if c == '`':
discard lex.advance()
return lex.scanBacktickString(startLoc)
# Char prefixes: c8' c16' c32' — must come before ident check
if c == 'c' and lex.peek(1) in {'8', '1', '3'}:
let d = lex.peek(1)
if d == '8' and lex.peek(2) == '\'':
discard lex.advance()
discard lex.advance()
return lex.scanChar(startLoc, 2)
elif d == '1' and lex.peek(2) == '6' and lex.peek(3) == '\'':
discard lex.advance()
discard lex.advance()
discard lex.advance()
return lex.scanChar(startLoc, 3)
elif d == '3' and lex.peek(2) == '2' and lex.peek(3) == '\'':
discard lex.advance()
discard lex.advance()
discard lex.advance()
return lex.scanChar(startLoc, 3)
if c == '\'':
# Check if this is a lifetime (e.g., 'a) or char literal (e.g., 'a')
# Lifetime: ' followed by identifier chars, no closing '
# Char literal: ' followed by one char/escape, then closing '
let afterQuote = lex.peek(1)
if isIdentStart(afterQuote):
# Could be lifetime or char literal like 'x'
# If next char after ident start is NOT ', it's a lifetime
# (for char literals, the char/escape is consumed and then ')
# Simple heuristic: if peek(2) is ', it's a char literal; else lifetime
if lex.peek(2) == '\'':
return lex.scanChar(startLoc, 0)
elif lex.peek(2) == '\0':
return lex.scanChar(startLoc, 0)
else:
# Lifetime: consume ' and then identifier chars
discard lex.advance() # '
while not lex.isAtEnd() and isIdentChar(lex.peek()):
discard lex.advance()
return lex.makeToken(tkLifetime, startLoc, startPos)
else:
return lex.scanChar(startLoc, 0)
if isIdentStart(c):
return lex.scanIdent(startLoc)
if c in {'0'..'9'}:
return lex.scanNumber(startLoc)
return lex.scanSymbol(startLoc)
proc tokenize*(lex: var Lexer): LexerResult =
while true:
let tok = lex.nextToken()
lex.tokens.add(tok)
if tok.kind == tkEndOfFile:
break
result = LexerResult(tokens: lex.tokens, diagnostics: lex.diagnostics)
proc tokenize*(source, sourceName: string): LexerResult =
var lex = initLexer(source, sourceName)
result = lex.tokenize()
proc dumpTokens*(res: LexerResult): string =
for tok in res.tokens:
result.add($tok & "\n")
+6
View File
@@ -0,0 +1,6 @@
import std/os
import cli
when isMainModule:
let args = commandLineParams()
quit(runCli(args))
+274
View File
@@ -0,0 +1,274 @@
import std/[strutils, os, tables, sequtils, strformat]
type
PackageType* = enum
ptExecutable
ptSharedLibrary
ptStaticLibrary
ptSource
DepKind* = enum
dkVersion ## "1.0" or "*"
dkPath ## { Path = "../Lib" }
dkGit ## { Version = "1.4", Source = "https://..." }
Dependency* = object
name*: string
case kind*: DepKind
of dkVersion:
versionReq*: string
of dkPath:
path*: string
of dkGit:
gitUrl*: string
gitVersion*: string
Manifest* = object
name*: string
version*: string
pkgType*: PackageType
output*: string
dependencies*: seq[Dependency]
devDependencies*: seq[Dependency]
buildDependencies*: seq[Dependency]
# ---------------------------------------------------------------------------
# Extended TOML parser (supports inline tables and arrays)
# ---------------------------------------------------------------------------
type
TomlValueKind = enum
tvkString, tvkTable, tvkArray, tvkInlineTable
TomlValue = object
case kind*: TomlValueKind
of tvkString:
strVal*: string
of tvkTable:
tableVal*: OrderedTableRef[string, TomlValue]
of tvkArray:
arrayVal*: seq[TomlValue]
of tvkInlineTable:
inlineVal*: OrderedTableRef[string, TomlValue]
proc parseInlineTable(s: string): OrderedTableRef[string, TomlValue] =
result = newOrderedTable[string, TomlValue]()
var content = s.strip()
if content.len >= 2 and content[0] == '{' and content[^1] == '}':
content = content[1 ..< ^1].strip()
var i = 0
while i < content.len:
# skip whitespace
while i < content.len and content[i] in Whitespace:
inc i
if i >= content.len: break
# parse key
var keyStart = i
while i < content.len and content[i] notin {'=', ' ', '\t'}:
inc i
let key = content[keyStart ..< i].strip()
# skip to =
while i < content.len and content[i] in Whitespace:
inc i
if i >= content.len or content[i] != '=': break
inc i
while i < content.len and content[i] in Whitespace:
inc i
# parse value
var valStart = i
if i < content.len and content[i] == '"':
inc i
while i < content.len and content[i] != '"':
inc i
if i < content.len: inc i
let val = content[valStart + 1 ..< i - 1]
result[key] = TomlValue(kind: tvkString, strVal: val)
elif i < content.len and content[i] == '{':
# nested inline table (skip for now)
var braceCount = 1
inc i
while i < content.len and braceCount > 0:
if content[i] == '{': inc braceCount
elif content[i] == '}': dec braceCount
inc i
let val = content[valStart ..< i]
result[key] = TomlValue(kind: tvkInlineTable, inlineVal: newOrderedTable[string, TomlValue]())
else:
while i < content.len and content[i] notin {',', ' ', '\t'}:
inc i
let val = content[valStart ..< i].strip()
result[key] = TomlValue(kind: tvkString, strVal: val)
# skip to comma or end
while i < content.len and content[i] in Whitespace:
inc i
if i < content.len and content[i] == ',':
inc i
proc parseArray(s: string): seq[TomlValue] =
result = @[]
var content = s.strip()
if content.len >= 2 and content[0] == '[' and content[^1] == ']':
content = content[1 ..< ^1].strip()
if content.len == 0: return
for item in content.split(','):
let val = item.strip()
if val.len >= 2 and val[0] == '"' and val[^1] == '"':
result.add(TomlValue(kind: tvkString, strVal: val[1 ..< ^1]))
else:
result.add(TomlValue(kind: tvkString, strVal: val))
proc parseValue(valStr: string): TomlValue =
let val = valStr.strip()
if val.len >= 2 and val[0] == '"' and val[^1] == '"':
return TomlValue(kind: tvkString, strVal: val[1 ..< ^1])
elif val.len >= 2 and val[0] == '[' and val[^1] == ']':
return TomlValue(kind: tvkArray, arrayVal: parseArray(val))
elif val.len >= 2 and val[0] == '{' and val[^1] == '}':
return TomlValue(kind: tvkInlineTable, inlineVal: parseInlineTable(val))
else:
return TomlValue(kind: tvkString, strVal: val)
proc parseToml*(path: string): OrderedTableRef[string, TomlValue] =
result = newOrderedTable[string, TomlValue]()
if not fileExists(path):
return result
let content = readFile(path)
var currentTable = ""
var currentArrayTable = ""
for rawLine in content.splitLines():
let line = rawLine.strip()
if line.len == 0 or line.startsWith("#"):
continue
if line.startsWith("[[") and line.endsWith("]]"):
# Array of tables
currentArrayTable = line[2 ..< ^2]
currentTable = ""
let newTable = TomlValue(kind: tvkTable, tableVal: newOrderedTable[string, TomlValue]())
if not result.hasKey(currentArrayTable):
result[currentArrayTable] = TomlValue(kind: tvkArray, arrayVal: @[newTable])
else:
result[currentArrayTable].arrayVal.add(newTable)
elif line.startsWith("[") and line.endsWith("]"):
currentTable = line[1 ..< ^1]
currentArrayTable = ""
if not result.hasKey(currentTable):
result[currentTable] = TomlValue(kind: tvkTable, tableVal: newOrderedTable[string, TomlValue]())
else:
let eqIdx = line.find('=')
if eqIdx >= 0:
let key = line[0 ..< eqIdx].strip()
let valStr = line[eqIdx + 1 .. ^1].strip()
let val = parseValue(valStr)
if currentArrayTable != "" and result.hasKey(currentArrayTable):
let arr = result[currentArrayTable].arrayVal
if arr.len > 0:
arr[^1].tableVal[key] = val
elif currentTable != "" and result.hasKey(currentTable):
result[currentTable].tableVal[key] = val
else:
result[key] = val
proc parseDepTable(table: OrderedTableRef[string, TomlValue]): seq[Dependency] =
result = @[]
for name, val in table:
case val.kind
of tvkString:
result.add(Dependency(name: name, kind: dkVersion, versionReq: val.strVal))
of tvkInlineTable:
let t = val.inlineVal
if t.hasKey("Path"):
result.add(Dependency(name: name, kind: dkPath, path: t["Path"].strVal))
elif t.hasKey("Source"):
var ver = "*"
if t.hasKey("Version"):
ver = t["Version"].strVal
result.add(Dependency(name: name, kind: dkGit, gitUrl: t["Source"].strVal, gitVersion: ver))
elif t.hasKey("Version"):
result.add(Dependency(name: name, kind: dkVersion, versionReq: t["Version"].strVal))
else:
result.add(Dependency(name: name, kind: dkVersion, versionReq: "*"))
else:
discard
# ---------------------------------------------------------------------------
# Lockfile
# ---------------------------------------------------------------------------
type
LockEntry* = object
name*: string
version*: string
source*: string
checksum*: string
Lockfile* = object
entries*: seq[LockEntry]
proc loadLockfile*(path: string): Lockfile =
result.entries = @[]
if not fileExists(path):
return result
let data = parseToml(path)
if data.hasKey("Package"):
let arr = data["Package"]
if arr.kind == tvkArray:
for item in arr.arrayVal:
if item.kind == tvkTable:
let t = item.tableVal
var entry = LockEntry()
if t.hasKey("Name"): entry.name = t["Name"].strVal
if t.hasKey("Version"): entry.version = t["Version"].strVal
if t.hasKey("Source"): entry.source = t["Source"].strVal
if t.hasKey("Checksum"): entry.checksum = t["Checksum"].strVal
result.entries.add(entry)
proc saveLockfile*(path: string, lock: Lockfile) =
var lines: seq[string] = @[]
for entry in lock.entries:
lines.add("[[Package]]")
lines.add(&"Name = \"{entry.name}\"")
lines.add(&"Version = \"{entry.version}\"")
lines.add(&"Source = \"{entry.source}\"")
if entry.checksum.len > 0:
lines.add(&"Checksum = \"{entry.checksum}\"")
lines.add("")
writeFile(path, lines.join("\n"))
# ---------------------------------------------------------------------------
# Manifest loader
# ---------------------------------------------------------------------------
proc loadManifest*(path: string): Manifest =
let data = parseToml(path)
result.name = ""
result.version = "0.1.0"
result.pkgType = ptExecutable
result.output = "Bin"
result.dependencies = @[]
result.devDependencies = @[]
result.buildDependencies = @[]
if data.hasKey("Package"):
let pkg = data["Package"].tableVal
if pkg.hasKey("Name"):
result.name = pkg["Name"].strVal
if pkg.hasKey("Version"):
result.version = pkg["Version"].strVal
if pkg.hasKey("Type"):
case pkg["Type"].strVal.toLowerAscii()
of "bin", "executable": result.pkgType = ptExecutable
of "shared", "sharedlibrary", "dll", "so", "dylib": result.pkgType = ptSharedLibrary
of "static", "staticlibrary", "lib", "a": result.pkgType = ptStaticLibrary
of "source": result.pkgType = ptSource
else: result.pkgType = ptExecutable
if data.hasKey("Build"):
let bld = data["Build"].tableVal
if bld.hasKey("Output"):
result.output = bld["Output"].strVal
if data.hasKey("Dependencies"):
result.dependencies = parseDepTable(data["Dependencies"].tableVal)
if data.hasKey("DevDependencies"):
result.devDependencies = parseDepTable(data["DevDependencies"].tableVal)
if data.hasKey("BuildDependencies"):
result.buildDependencies = parseDepTable(data["BuildDependencies"].tableVal)
File diff suppressed because it is too large Load Diff
+45
View File
@@ -0,0 +1,45 @@
import std/tables
import types, ast, source_location
type
SymbolKind* = enum
skVar
skFunc
skType
skConst
skModule
Symbol* = ref object
kind*: SymbolKind
name*: string
typ*: Type
decl*: Decl ## optional back-reference to AST decl
isMutable*: bool
isPublic*: bool
Scope* = ref object
parent*: Scope
table*: Table[string, Symbol] ## O(1) lookup via hash table
proc newScope*(parent: Scope = nil): Scope =
result = Scope(parent: parent)
proc define*(scope: Scope, sym: Symbol): bool =
## Returns false if name already exists in this scope
if scope.table.hasKey(sym.name):
return false
scope.table[sym.name] = sym
return true
proc lookup*(scope: Scope, name: string): Symbol =
var cur = scope
while cur != nil:
if cur.table.hasKey(name):
return cur.table[name]
cur = cur.parent
return nil
proc lookupLocal*(scope: Scope, name: string): Symbol =
if scope.table.hasKey(name):
return scope.table[name]
return nil
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
type
SourceLocation* = object
line*: uint32 ## 1-based
column*: uint32 ## 1-based (UTF-8 byte offset in line)
offset*: uint32 ## byte offset from start of file
proc `$`*(loc: SourceLocation): string =
$loc.line & ":" & $loc.column
+336
View File
@@ -0,0 +1,336 @@
import source_location
type
TokenKind* = enum
##Literals
tkIntLiteral # 42 0xFF 0b1010 0o77
tkFloatLiteral # 3.14 1.0e-9
tkStringLiteral # "hello" c8"hello" c16"hello" c32"hello" `raw multi-line`
tkCharLiteral # 'A' c8'A' c16'A' c32'A'
tkBoolLiteral # true false
##Identifiers
tkIdent # foo Bar _x
tkUnderscore # _
##Control flow keywords
tkIf # if
tkElse # else
tkWhile # while
tkDo # do
tkLoop # loop
tkFor # for
tkIn # in
tkBreak # break
tkContinue # continue
tkReturn # return
tkMatch # match
##Declaration keywords
tkFunc # func
tkLet # let
tkVar # var
tkConst # const
tkType # type
tkStruct # struct
tkEnum # enum
tkUnion # union
tkInterface # interface
tkExtend # extend
tkModule # module
tkImport # import
tkPub # pub
tkExtern # extern
##Other keywords
tkAs # as
tkIs # is
tkNull # null
tkSelf # self
tkSuper # super
tkSizeOf # sizeof
tkOwn # own (gradual ownership transfer)
tkMut # mut (mutable reference)
tkDiscard # discard (evaluate and throw away)
tkAsync # async
tkAwait # await
tkSpawn # spawn
tkStaticAssert # static_assert
tkComptime # comptime
tkDyn # dyn
tkLifetime # 'a (lifetime parameter)
##Punctuation
tkLParen # (
tkRParen # )
tkLBrace # {
tkRBrace # }
tkLBracket # [
tkRBracket # ]
tkComma # ,
tkSemicolon # ;
tkColon # :
tkColonColon # ::
tkDot # .
tkDotDot # ..
tkDotDotDot # ...
tkDotDotEqual # ..=
tkArrow # ->
tkFatArrow # =>
tkAt # @
tkHash # #
tkQuestion # ?
##Arithmetic operators
tkPlus # +
tkMinus # -
tkStar # *
tkSlash # /
tkPercent # %
tkStarStar # **
tkPlusPlus # ++
tkMinusMinus # --
##Bitwise operators
tkAmp # &
tkPipe # |
tkCaret # ^
tkTilde # ~
tkShl # <<
tkShr # >>
##Logical operators
tkAmpAmp # &&
tkPipePipe # ||
tkBang # !
##Comparison operators
tkEq # ==
tkNe # !=
tkLt # <
tkLe # <=
tkGt # >
tkGe # >=
##Assignment operators
tkAssign # =
tkPlusAssign # +=
tkMinusAssign # -=
tkStarAssign # *=
tkSlashAssign # /=
tkPercentAssign # %=
tkAmpAssign # &=
tkPipeAssign # |=
tkCaretAssign # ^=
tkShlAssign # <<=
tkShrAssign # >>=
##Compile-time intrinsics
tkHashLine # #line
tkHashColumn # #column
tkHashFile # #file
tkHashFunction # #function
tkHashDate # #date
tkHashTime # #time
tkHashModule # #module
tkHashEmit # #emit
##Special
tkNewLine # significant newline (if grammar uses them)
tkEndOfFile # end of file
tkUnknown # unrecognized character
Token* = object
kind*: TokenKind
text*: string # original source spelling
loc*: SourceLocation
proc isKeyword*(kind: TokenKind): bool =
case kind
of 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, tkOwn, tkMut, tkDiscard, tkAsync, tkAwait, tkSpawn, tkStaticAssert, tkComptime, tkDyn:
true
else:
false
proc isLiteral*(kind: TokenKind): bool =
case kind
of tkIntLiteral, tkFloatLiteral, tkStringLiteral, tkCharLiteral, tkBoolLiteral:
true
else:
false
proc isOperator*(kind: TokenKind): bool =
case kind
of tkPlus..tkShrAssign:
true
else:
false
proc isEof*(kind: TokenKind): bool = kind == tkEndOfFile
proc keywordKind*(text: string): TokenKind =
case text
of "func": tkFunc
of "let": tkLet
of "var": tkVar
of "const": tkConst
of "type": tkType
of "struct": tkStruct
of "enum": tkEnum
of "union": tkUnion
of "interface": tkInterface
of "extend": tkExtend
of "module": tkModule
of "import": tkImport
of "pub": tkPub
of "extern": tkExtern
of "if": tkIf
of "else": tkElse
of "while": tkWhile
of "do": tkDo
of "loop": tkLoop
of "for": tkFor
of "in": tkIn
of "break": tkBreak
of "continue": tkContinue
of "return": tkReturn
of "match": tkMatch
of "as": tkAs
of "is": tkIs
of "null": tkNull
of "self": tkSelf
of "super": tkSuper
of "sizeof": tkSizeOf
of "own": tkOwn
of "mut": tkMut
of "discard": tkDiscard
of "async": tkAsync
of "await": tkAwait
of "spawn": tkSpawn
of "static_assert": tkStaticAssert
of "comptime": tkComptime
of "dyn": tkDyn
of "true", "false": tkBoolLiteral
else: tkIdent
proc tokenKindName*(kind: TokenKind): string =
case kind
of tkIntLiteral: "integer literal"
of tkFloatLiteral: "float literal"
of tkStringLiteral: "string literal"
of tkCharLiteral: "char literal"
of tkBoolLiteral: "boolean literal"
of tkIdent: "identifier"
of tkUnderscore: "'_'"
of tkSizeOf: "'sizeof'"
of tkIf: "'if'"
of tkElse: "'else'"
of tkWhile: "'while'"
of tkDo: "'do'"
of tkLoop: "'loop'"
of tkFor: "'for'"
of tkIn: "'in'"
of tkBreak: "'break'"
of tkContinue: "'continue'"
of tkReturn: "'return'"
of tkMatch: "'match'"
of tkFunc: "'func'"
of tkLet: "'let'"
of tkVar: "'var'"
of tkConst: "'const'"
of tkType: "'type'"
of tkStruct: "'struct'"
of tkEnum: "'enum'"
of tkUnion: "'union'"
of tkInterface: "'interface'"
of tkExtend: "'extend'"
of tkModule: "'module'"
of tkImport: "'import'"
of tkPub: "'pub'"
of tkExtern: "'extern'"
of tkAs: "'as'"
of tkIs: "'is'"
of tkNull: "'null'"
of tkSelf: "'self'"
of tkSuper: "'super'"
of tkOwn: "'own'"
of tkMut: "'mut'"
of tkDiscard: "'discard'"
of tkAsync: "'async'"
of tkAwait: "'await'"
of tkSpawn: "'spawn'"
of tkStaticAssert: "'static_assert'"
of tkComptime: "'comptime'"
of tkDyn: "'dyn'"
of tkLifetime: "lifetime"
of tkLParen: "'('"
of tkRParen: "')'"
of tkLBrace: "'{'"
of tkRBrace: "'}'"
of tkLBracket: "'['"
of tkRBracket: "']'"
of tkComma: "','"
of tkSemicolon: "';'"
of tkColon: "':'"
of tkColonColon: "'::'"
of tkDot: "'.'"
of tkDotDot: "'..'"
of tkDotDotDot: "'...'"
of tkDotDotEqual: "'..='"
of tkArrow: "'->'"
of tkFatArrow: "'=>'"
of tkAt: "'@'"
of tkHash: "'#'"
of tkQuestion: "'?'"
of tkPlus: "'+'"
of tkMinus: "'-'"
of tkStar: "'*'"
of tkSlash: "'/'"
of tkPercent: "'%'"
of tkStarStar: "'**'"
of tkPlusPlus: "'++'"
of tkMinusMinus: "'--'"
of tkAmp: "'&'"
of tkPipe: "'|'"
of tkCaret: "'^'"
of tkTilde: "'~'"
of tkShl: "'<<'"
of tkShr: "'>>'"
of tkAmpAmp: "'&&'"
of tkPipePipe: "'||'"
of tkBang: "'!'"
of tkEq: "'=='"
of tkNe: "'!='"
of tkLt: "'<'"
of tkLe: "'<='"
of tkGt: "'>'"
of tkGe: "'>='"
of tkAssign: "'='"
of tkPlusAssign: "'+='"
of tkMinusAssign: "'-='"
of tkStarAssign: "'*='"
of tkSlashAssign: "'/='"
of tkPercentAssign: "'%='"
of tkAmpAssign: "'&='"
of tkPipeAssign: "'|='"
of tkCaretAssign: "'^='"
of tkShlAssign: "'<<='"
of tkShrAssign: "'>>='"
of tkHashLine: "'#line'"
of tkHashColumn: "'#column'"
of tkHashFile: "'#file'"
of tkHashFunction: "'#function'"
of tkHashDate: "'#date'"
of tkHashTime: "'#time'"
of tkHashModule: "'#module'"
of tkHashEmit: "'#emit'"
of tkNewLine: "newline"
of tkEndOfFile: "end of file"
of tkUnknown: "unknown token"
proc `$`*(tok: Token): string =
result = tokenKindName(tok.kind) & " '" & tok.text & "' @ " & $tok.loc
+231
View File
@@ -0,0 +1,231 @@
import std/[sequtils, strformat, strutils]
type
TypeKind* = enum
tkUnknown
tkVoid
tkBool
tkBool8
tkBool16
tkBool32
tkChar8
tkChar16
tkChar32
tkStr
tkInt8
tkInt16
tkInt32
tkInt64
tkInt
tkUInt8
tkUInt16
tkUInt32
tkUInt64
tkUInt
tkFloat32
tkFloat64
tkPointer
tkRef
tkMutRef
tkDynRef
tkSlice
tkRange
tkTuple
tkNamed
tkTypeParam
tkFunc
Type* = ref object
kind*: TypeKind
name*: string
inner*: seq[Type] ## for Pointer(pointee), Slice(element), Tuple(elements), Func(params+ret)
# Factories
proc makeUnknown*(): Type = Type(kind: tkUnknown)
proc makeVoid*(): Type = Type(kind: tkVoid)
proc makeBool*(): Type = Type(kind: tkBool)
proc makeBool8*(): Type = Type(kind: tkBool8)
proc makeBool16*(): Type = Type(kind: tkBool16)
proc makeBool32*(): Type = Type(kind: tkBool32)
proc makeChar8*(): Type = Type(kind: tkChar8)
proc makeChar16*(): Type = Type(kind: tkChar16)
proc makeChar32*(): Type = Type(kind: tkChar32)
proc makeStr*(): Type = Type(kind: tkStr)
proc makeInt8*(): Type = Type(kind: tkInt8)
proc makeInt16*(): Type = Type(kind: tkInt16)
proc makeInt32*(): Type = Type(kind: tkInt32)
proc makeInt64*(): Type = Type(kind: tkInt64)
proc makeInt*(): Type = Type(kind: tkInt)
proc makeUInt8*(): Type = Type(kind: tkUInt8)
proc makeUInt16*(): Type = Type(kind: tkUInt16)
proc makeUInt32*(): Type = Type(kind: tkUInt32)
proc makeUInt64*(): Type = Type(kind: tkUInt64)
proc makeUInt*(): Type = Type(kind: tkUInt)
proc makeFloat32*(): Type = Type(kind: tkFloat32)
proc makeFloat64*(): Type = Type(kind: tkFloat64)
proc makePointer*(pointee: Type): Type =
Type(kind: tkPointer, inner: @[pointee])
proc makeRef*(pointee: Type): Type =
Type(kind: tkRef, inner: @[pointee])
proc makeMutRef*(pointee: Type): Type =
Type(kind: tkMutRef, inner: @[pointee])
proc makeDynRef*(interfaceName: string): Type =
Type(kind: tkDynRef, name: interfaceName)
proc makeSlice*(element: Type): Type =
Type(kind: tkSlice, inner: @[element])
proc makeRange*(element: Type): Type =
Type(kind: tkRange, inner: @[element])
proc makeTuple*(elems: seq[Type]): Type =
Type(kind: tkTuple, inner: elems)
proc makeNamed*(name: string): Type =
Type(kind: tkNamed, name: name)
proc makeTypeParam*(name: string): Type =
Type(kind: tkTypeParam, name: name)
proc makeFunc*(params: seq[Type], ret: Type): Type =
Type(kind: tkFunc, inner: params & @[ret])
# Predicates
proc isUnknown*(t: Type): bool = t.kind == tkUnknown
proc isVoid*(t: Type): bool = t.kind == tkVoid
proc isBool*(t: Type): bool = t.kind in {tkBool, tkBool8, tkBool16, tkBool32}
proc isNumeric*(t: Type): bool =
if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true
t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt,
tkUInt8, tkUInt16, tkUInt32, tkUInt64, tkUInt,
tkFloat32, tkFloat64}
proc isInteger*(t: Type): bool =
if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true
t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt,
tkUInt8, tkUInt16, tkUInt32, tkUInt64, tkUInt}
proc isFloat*(t: Type): bool =
if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true
t.kind in {tkFloat32, tkFloat64}
proc isSigned*(t: Type): bool =
if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true
t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt}
proc isPointer*(t: Type): bool = t.kind in {tkPointer, tkRef, tkMutRef, tkDynRef}
proc isRawPointer*(t: Type): bool = t.kind == tkPointer
proc isRef*(t: Type): bool = t.kind == tkRef
proc isMutRef*(t: Type): bool = t.kind == tkMutRef
proc isDynRef*(t: Type): bool = t.kind == tkDynRef
proc isSlice*(t: Type): bool = t.kind == tkSlice
# Comparison
proc `==`*(a, b: Type): bool =
if a.isNil or b.isNil:
return a.isNil and b.isNil
if a.kind != b.kind: return false
if a.kind in {tkNamed, tkTypeParam} and a.name != b.name: return false
if a.inner.len != b.inner.len: return false
for i in 0 ..< a.inner.len:
if a.inner[i] != b.inner[i]: return false
return true
proc `!=`*(a, b: Type): bool = not (a == b)
# Assignment compatibility
proc isAssignableTo*(a, b: Type): bool =
if a.isUnknown or b.isUnknown: return true
if b.kind == tkTypeParam: return true
if a == b: return true
# float32 -> float64
if a.kind == tkFloat32 and b.kind == tkFloat64: return true
# int64 <-> int (on x64)
if a.kind == tkInt64 and b.kind == tkInt: return true
if a.kind == tkInt and b.kind == tkInt64: return true
if a.kind == tkUInt64 and b.kind == tkUInt: return true
if a.kind == tkUInt and b.kind == tkUInt64: return true
# int32 <-> int (for convenience in bootstrap)
if a.kind == tkInt32 and b.kind == tkInt: return true
if a.kind == tkInt and b.kind == tkInt32: return true
if a.kind == tkUInt32 and b.kind == tkInt: return true
if a.kind == tkInt and b.kind == tkUInt32: return true
if a.kind == tkUInt32 and b.kind == tkUInt: return true
if a.kind == tkUInt and b.kind == tkUInt32: return true
if a.kind == tkInt32 and b.kind == tkUInt32: return true
if a.kind == tkUInt32 and b.kind == tkInt32: return true
# smaller int -> int/uint
if b.kind == tkInt and a.kind in {tkInt8, tkInt16, tkInt32}: return true
if b.kind == tkUInt and a.kind in {tkUInt8, tkUInt16, tkUInt32}: return true
# int <-> uint (for convenience in bootstrap)
if a.kind == tkInt and b.kind == tkUInt: return true
if a.kind == tkUInt and b.kind == tkInt: return true
# *char8 -> String (C string literal to Bux String)
if a.kind == tkPointer and b.kind == tkStr:
if a.inner.len > 0 and a.inner[0].kind == tkChar8:
return true
# numeric exact match required otherwise
if a.isNumeric and b.isNumeric: return false
# bool across widths
if a.isBool and b.isBool: return true
# pointer to opaque / null pointer
if a.isPointer and b.isPointer:
if a.inner.len > 0 and a.inner[0].isUnknown:
return true
if b.inner.len > 0 and b.inner[0].isUnknown:
return true
# &mut T -> &T (mutable ref can coerce to shared ref)
if a.isMutRef and b.isRef:
if a.inner.len > 0 and b.inner.len > 0 and a.inner[0].isAssignableTo(b.inner[0]):
return true
# &mut T -> *T (mutable ref can coerce to raw pointer)
if a.isMutRef and b.isRawPointer:
if a.inner.len > 0 and b.inner.len > 0 and a.inner[0].isAssignableTo(b.inner[0]):
return true
# &T -> *T (shared ref can coerce to raw pointer)
if a.isRef and b.isRawPointer:
if a.inner.len > 0 and b.inner.len > 0 and a.inner[0].isAssignableTo(b.inner[0]):
return true
# &Concrete -> &dyn Trait (trait object coercion)
if b.isDynRef and a.isRef:
return true
if b.isDynRef and a.isMutRef:
return true
return false
# String representation
proc toString*(t: Type): string =
case t.kind
of tkUnknown: "?"
of tkVoid: "void"
of tkBool: "bool"
of tkBool8: "bool8"
of tkBool16: "bool16"
of tkBool32: "bool32"
of tkChar8: "char8"
of tkChar16: "char16"
of tkChar32: "char32"
of tkStr: "String"
of tkInt8: "int8"
of tkInt16: "int16"
of tkInt32: "int32"
of tkInt64: "int64"
of tkInt: "int"
of tkUInt8: "uint8"
of tkUInt16: "uint16"
of tkUInt32: "uint32"
of tkUInt64: "uint64"
of tkUInt: "uint"
of tkFloat32: "float32"
of tkFloat64: "float64"
of tkPointer: "*" & t.inner[0].toString
of tkRef: "&" & t.inner[0].toString
of tkMutRef: "&mut " & t.inner[0].toString
of tkDynRef: "&dyn " & t.name
of tkSlice:
if t.inner.len > 0: t.inner[0].toString & "[]"
else: "Slice<?>"
of tkRange:
if t.inner.len > 0: "Range<" & t.inner[0].toString & ">"
else: "Range<?>"
of tkTuple:
"(" & t.inner.mapIt(it.toString).join(", ") & ")"
of tkNamed: t.name
of tkTypeParam: t.name
of tkFunc:
if t.inner.len == 0: "func()"
else:
let params = t.inner[0..^2].mapIt(it.toString).join(", ")
let ret = t.inner[^1].toString
"func(" & params & ") -> " & ret
+431
View File
@@ -0,0 +1,431 @@
// ast.bux — AST node types (Expr, Stmt, Decl, Pattern, TypeExpr)
module Ast {
// ---------------------------------------------------------------------------
// SourceLocation (inline for convenience)
// ---------------------------------------------------------------------------
struct SourceLoc {
line: uint32,
column: uint32,
}
// ---------------------------------------------------------------------------
// Token (lightweight inline)
// ---------------------------------------------------------------------------
struct AstToken {
kind: int,
text: String,
line: uint32,
column: uint32,
}
// ---------------------------------------------------------------------------
// TypeExpr — type expressions
// ---------------------------------------------------------------------------
const tekNamed: int = 0;
const tekPath: int = 1;
const tekSlice: int = 2;
const tekPointer: int = 3;
const tekTuple: int = 4;
const tekSelf: int = 5;
struct TypeExpr {
kind: int,
line: uint32,
column: uint32,
typeName: String, // for tekNamed
pathStr: String, // for tekPath (segments joined with ::)
pathCount: int, // number of path segments
typeArgName0: String, // up to 2 type args
typeArgName1: String,
typeArgCount: int,
sliceElement: *TypeExpr, // for tekSlice
pointerPointee: *TypeExpr, // for tekPointer
}
// ---------------------------------------------------------------------------
// Pattern — match patterns
// ---------------------------------------------------------------------------
const pkWildcard: int = 0;
const pkLiteral: int = 1;
const pkIdent: int = 2;
const pkRange: int = 3;
const pkEnum: int = 4;
const pkStruct: int = 5;
const pkTuple: int = 6;
struct Pattern {
kind: int,
line: uint32,
column: uint32,
patIdent: String, // for pkIdent
patLitKind: int, // for pkLiteral (token kind)
patLitText: String, // for pkLiteral (token text)
patRangeInclusive: bool, // for pkRange
patEnumPath: String, // for pkEnum (path joined)
patStructName: String, // for pkStruct
}
// ---------------------------------------------------------------------------
// Expr — expressions (tagged union)
// ---------------------------------------------------------------------------
const ekLiteral: int = 0;
const ekIdent: int = 1;
const ekSelf: int = 2;
const ekPath: int = 3;
const ekSizeOf: int = 4;
const ekUnary: int = 5;
const ekPostfix: int = 6;
const ekBinary: int = 7;
const ekAssign: int = 8;
const ekTernary: int = 9;
const ekRange: int = 10;
const ekCall: int = 11;
const ekGenericCall: int = 12;
const ekIndex: int = 13;
const ekField: int = 14;
const ekStructInit: int = 15;
const ekSlice: int = 16;
const ekTuple: int = 17;
const ekCast: int = 18;
const ekIs: int = 19;
const ekTry: int = 20;
const ekUnwrap: int = 23;
const ekBlock: int = 21;
const ekMatch: int = 22;
const ekSpawn: int = 24;
const ekAwait: int = 25;
struct ExprList {
expr: *Expr,
next: *ExprList,
}
struct Expr {
kind: int,
line: uint32,
column: uint32,
// Common fields
strValue: String, // ident name, path segments, field name, callee
intValue: int, // operator kind, intrinsic kind
boolValue: bool, // range inclusive
tokKind: int, // literal token kind
tokText: String, // literal token text
// Children (up to 3 sub-expressions)
child1: *Expr, // left, operand, callee, cond, subject
child2: *Expr, // right, index, then, value
child3: *Expr, // else, third
// Extra references
refType: *TypeExpr, // cast type, is type, sizeof type
refBlock: *Block, // for ekBlock
// Generic call
genericCallee: String,
genericTypeArg0: String,
genericTypeArg1: String,
genericTypeArgCount: int,
// Struct init fields
structName: String,
structFieldCount: int,
// Call arguments (linked list for multi-arg support)
callArgs: *ExprList,
callArgCount: int,
}
// ---------------------------------------------------------------------------
// Block — sequence of statements
// ---------------------------------------------------------------------------
struct Block {
line: uint32,
column: uint32,
stmtCount: int,
firstStmt: *Stmt,
lastStmt: *Stmt,
}
// ---------------------------------------------------------------------------
// Stmt — statements
// ---------------------------------------------------------------------------
const skExpr: int = 0;
const skLet: int = 1;
const skIf: int = 2;
const skWhile: int = 3;
const skDoWhile: int = 4;
const skLoop: int = 5;
const skFor: int = 6;
const skMatch: int = 7;
const skReturn: int = 8;
const skBreak: int = 9;
const skContinue: int = 10;
const skDecl: int = 11;
struct ElseIf {
line: uint32;
column: uint32;
cond: *Expr;
block: *Block;
}
struct Stmt {
kind: int,
line: uint32,
column: uint32,
// Common fields
strValue: String, // let name, pattern ident, label, for var
boolValue: bool, // let mutable
// Children
child1: *Expr, // init expr, condition, iter expr, return value
child2: *Expr, // match subject
child3: *Expr, // extra
refStmtType: *TypeExpr, // let type annotation
refStmtPattern: *Pattern,// let pattern
refStmtDecl: *Decl, // for skDecl
refStmtBlock: *Block, // then/body block
refStmtElse: *Block, // else block
// Else-if chain
elseIfCount: int,
// Linked list
nextStmt: *Stmt,
}
// ---------------------------------------------------------------------------
// Decl — declarations
// ---------------------------------------------------------------------------
const dkFunc: int = 0;
const dkStruct: int = 1;
const dkEnum: int = 2;
const dkUnion: int = 3;
const dkInterface: int = 4;
const dkImpl: int = 5;
const dkModule: int = 6;
const dkUse: int = 7;
const dkConst: int = 8;
const dkTypeAlias: int = 9;
const dkExternFunc: int = 10;
const dkExternVar: int = 11;
struct Param {
line: uint32;
column: uint32;
name: String;
refParamType: *TypeExpr;
isVariadic: bool;
}
struct StructField {
line: uint32;
column: uint32;
isPublic: bool;
name: String;
refFieldType: *TypeExpr;
}
struct EnumVariant {
line: uint32;
column: uint32;
name: String;
fieldCount: int;
fieldTypeName0: String;
fieldTypeName1: String;
}
struct Decl {
kind: int,
line: uint32,
column: uint32,
isPublic: bool,
isAsync: bool,
// Names
strValue: String, // decl name
strValue2: String, // interface name, dll name, module path
// Type params (up to 2)
typeParam0: String,
typeParam1: String,
typeParamCount: int,
// Params (for functions)
paramCount: int,
param0: Param,
param1: Param,
param2: Param,
param3: Param,
param4: Param,
param5: Param,
param6: Param,
param7: Param,
param8: Param,
retType: *TypeExpr,
// Body
refBody: *Block,
// Struct fields (up to 64)
fieldCount: int,
field0: StructField,
field1: StructField,
field2: StructField,
field3: StructField,
field4: StructField,
field5: StructField,
field6: StructField,
field7: StructField,
field8: StructField,
field9: StructField,
field10: StructField,
field11: StructField,
field12: StructField,
field13: StructField,
field14: StructField,
field15: StructField,
field16: StructField,
field17: StructField,
field18: StructField,
field19: StructField,
field20: StructField,
field21: StructField,
field22: StructField,
field23: StructField,
field24: StructField,
field25: StructField,
field26: StructField,
field27: StructField,
field28: StructField,
field29: StructField,
field30: StructField,
field31: StructField,
field32: StructField,
field33: StructField,
field34: StructField,
field35: StructField,
field36: StructField,
field37: StructField,
field38: StructField,
field39: StructField,
field40: StructField,
field41: StructField,
field42: StructField,
field43: StructField,
field44: StructField,
field45: StructField,
field46: StructField,
field47: StructField,
field48: StructField,
field49: StructField,
field50: StructField,
field51: StructField,
field52: StructField,
field53: StructField,
field54: StructField,
field55: StructField,
field56: StructField,
field57: StructField,
field58: StructField,
field59: StructField,
field60: StructField,
field61: StructField,
field62: StructField,
field63: StructField,
// Enum variants (up to 8)
variantCount: int,
variant0: EnumVariant,
variant1: EnumVariant,
variant2: EnumVariant,
variant3: EnumVariant,
variant4: EnumVariant,
variant5: EnumVariant,
variant6: EnumVariant,
variant7: EnumVariant,
// Impl methods (up to 4)
methodCount: int,
// Use/import
useKind: int,
usePath: String,
useNames: String, // joined names for multi-import
// Const
constType: *TypeExpr,
constValue: *Expr,
// Type alias
aliasType: *TypeExpr,
// Extern func
extFuncDll: String,
extFuncVariadic: bool,
extFuncRetType: *TypeExpr,
// Children
childDecl1: *Decl, // linked list of decls (for module items, impl methods)
childDecl2: *Decl,
}
// ---------------------------------------------------------------------------
// Module — AST root
// ---------------------------------------------------------------------------
struct Module {
name: String,
path: String, // path segments joined
itemCount: int,
firstItem: *Decl,
}
// ---------------------------------------------------------------------------
// Constructor helpers
// ---------------------------------------------------------------------------
func Ast_MakeExpr(kind: int, line: uint32, col: uint32) -> Expr {
return Expr { kind: kind, line: line, column: col,
strValue: "", intValue: 0, boolValue: false,
tokKind: 0, tokText: "",
child1: null as *Expr, child2: null as *Expr, child3: null as *Expr,
refType: null as *TypeExpr, refBlock: null as *Block,
genericCallee: "", genericTypeArg0: "", genericTypeArg1: "", genericTypeArgCount: 0,
structName: "", structFieldCount: 0,
callArgs: null as *ExprList, callArgCount: 0 };
}
func Ast_MakeIdent(name: String, line: uint32, col: uint32) -> Expr {
var e: Expr = Ast_MakeExpr(ekIdent, line, col);
e.strValue = name;
return e;
}
func Ast_MakeLiteral(tokKind: int, text: String, line: uint32, col: uint32) -> Expr {
var e: Expr = Ast_MakeExpr(ekLiteral, line, col);
e.tokKind = tokKind;
e.tokText = text;
return e;
}
func Ast_MakeBinary(op: int, left: *Expr, right: *Expr, line: uint32, col: uint32) -> Expr {
var e: Expr = Ast_MakeExpr(ekBinary, line, col);
e.intValue = op;
e.child1 = left;
e.child2 = right;
return e;
}
func Ast_MakeCall(callee: *Expr, line: uint32, col: uint32) -> Expr {
var e: Expr = Ast_MakeExpr(ekCall, line, col);
e.child1 = callee;
return e;
}
func Ast_MakeStmt(kind: int, line: uint32, col: uint32) -> Stmt {
return Stmt { kind: kind, line: line, column: col,
strValue: "", boolValue: false,
child1: null as *Expr, child2: null as *Expr, child3: null as *Expr,
refStmtType: null as *TypeExpr, refStmtPattern: null as *Pattern,
refStmtDecl: null as *Decl, refStmtBlock: null as *Block, refStmtElse: null as *Block,
elseIfCount: 0 };
}
func Ast_MakeDecl(kind: int, line: uint32, col: uint32) -> Decl {
return Decl { kind: kind, line: line, column: col, isPublic: false,
strValue: "", strValue2: "",
typeParam0: "", typeParam1: "", typeParamCount: 0,
paramCount: 0,
retType: null as *TypeExpr,
refBody: null as *Block,
fieldCount: 0,
variantCount: 0,
methodCount: 0,
useKind: 0, usePath: "", useNames: "",
constType: null as *TypeExpr, constValue: null as *Expr,
aliasType: null as *TypeExpr,
extFuncDll: "", extFuncVariadic: false, extFuncRetType: null as *TypeExpr,
childDecl1: null as *Decl, childDecl2: null as *Decl };
}
}
+689
View File
@@ -0,0 +1,689 @@
// c_backend.bux — C transpiler backend (ported from c_backend.nim)
// Generates C code from the HIR.
module CBackend {
// ---------------------------------------------------------------------------
// Type → C type name
// ---------------------------------------------------------------------------
func CBackend_TypeToC(kind: int) -> String {
if kind == tyVoid { return "void"; }
if kind == tyBool { return "bool"; }
if kind == tyBool8 { return "bool"; }
if kind == tyBool16 { return "bool"; }
if kind == tyBool32 { return "bool"; }
if kind == tyChar8 { return "char"; }
if kind == tyChar16 { return "uint16"; }
if kind == tyChar32 { return "uint32"; }
if kind == tyStr { return "String"; }
if kind == tyInt8 { return "int8"; }
if kind == tyInt16 { return "int16"; }
if kind == tyInt32 { return "int32"; }
if kind == tyInt64 { return "int64"; }
if kind == tyInt{ return "int"; }
if kind == tyUInt8 { return "uint8"; }
if kind == tyUInt16 { return "uint16"; }
if kind == tyUInt32 { return "uint32"; }
if kind == tyUInt64 { return "uint64"; }
if kind == tyUInt { return "uint"; }
if kind == tyFloat32 { return "float32"; }
if kind == tyFloat64 { return "float64"; }
if kind == tyPointer { return "void*"; }
if kind == tyNamed { return "int"; }
return "int";
}
func CBackend_OpToC(op: int) -> String {
if op == tkPlus { return "+"; }
if op == tkMinus { return "-"; }
if op == tkStar { return "*"; }
if op == tkSlash { return "/"; }
if op == tkPercent { return "%"; }
if op == tkEq { return "=="; }
if op == tkNe { return "!="; }
if op == tkLt { return "<"; }
if op == tkLe { return "<="; }
if op == tkGt { return ">"; }
if op == tkGe { return ">="; }
if op == tkAmpAmp { return "&&"; }
if op == tkPipePipe { return "||"; }
if op == tkBang { return "!"; }
if op == tkAmp { return "&"; }
if op == tkPipe { return "|"; }
if op == tkCaret { return "^"; }
if op == tkShl { return "<<"; }
if op == tkShr { return ">>"; }
if op == tkAssign { return "="; }
return "?";
}
// ---------------------------------------------------------------------------
// StringBuilder-based C emitter
// ---------------------------------------------------------------------------
struct CEmitter {
sb: StringBuilder,
indent: int,
}
func CBE_Init() -> CEmitter {
var sb: StringBuilder = StringBuilder_NewCap(4096);
return CEmitter { sb: sb, indent: 0 };
}
func CBE_Emit(cbe: *CEmitter, text: String) {
var i: int = 0;
while i < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
i = i + 1;
}
StringBuilder_Append(&cbe.sb, text);
}
func CBE_EmitLine(cbe: *CEmitter, text: String) {
CBE_Emit(cbe, text);
StringBuilder_Append(&cbe.sb, "\n");
}
func CBE_Build(cbe: *CEmitter) -> String {
return StringBuilder_Build(&cbe.sb);
}
// ---------------------------------------------------------------------------
// Emit HIR node
// ---------------------------------------------------------------------------
func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
if node == null as *HirNode { return; }
let kind: int = node.kind;
// Literal
if kind == hLit {
if node.intValue == tkNull {
StringBuilder_Append(&cbe.sb, "0");
} else {
StringBuilder_Append(&cbe.sb, node.strValue);
}
return;
}
// Variable
if kind == hVar {
StringBuilder_Append(&cbe.sb, node.strValue);
return;
}
// Binary
if kind == hBinary {
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, CBackend_OpToC(node.intValue));
StringBuilder_Append(&cbe.sb, " ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// Unary
if kind == hUnary {
StringBuilder_Append(&cbe.sb, CBackend_OpToC(node.intValue));
CBE_EmitExpr(cbe, node.child1);
return;
}
// Call
if kind == hCall {
StringBuilder_Append(&cbe.sb, node.strValue);
StringBuilder_Append(&cbe.sb, "(");
var needsComma: bool = false;
if node.child1 != null as *HirNode {
CBE_EmitExpr(cbe, node.child1);
needsComma = true;
}
if node.child2 != null as *HirNode {
if needsComma {
StringBuilder_Append(&cbe.sb, ", ");
}
CBE_EmitExpr(cbe, node.child2);
needsComma = true;
}
// Emit extra args from linked list
var ai: int = 0;
var curExtra: *HirArgList = node.extraData as *HirArgList;
while ai < node.extraCount {
if needsComma {
StringBuilder_Append(&cbe.sb, ", ");
}
CBE_EmitExpr(cbe, curExtra.node);
needsComma = true;
curExtra = curExtra.next;
ai = ai + 1;
}
StringBuilder_Append(&cbe.sb, ")");
return;
}
// spawn Callee(args)
if kind == hSpawn {
StringBuilder_Append(&cbe.sb, "bux_async_spawn(");
StringBuilder_Append(&cbe.sb, node.strValue);
if node.child1 != null as *HirNode {
StringBuilder_Append(&cbe.sb, ", (void*)");
CBE_EmitExpr(cbe, node.child1);
}
StringBuilder_Append(&cbe.sb, ")");
return;
}
// await
if kind == hAwait {
StringBuilder_Append(&cbe.sb, "bux_async_await(");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Return
if kind == hReturn {
StringBuilder_Append(&cbe.sb, "return");
if node.child1 != null as *HirNode {
StringBuilder_Append(&cbe.sb, " ");
CBE_EmitExpr(cbe, node.child1);
}
return;
}
// Alloca
if kind == hAlloca {
if !String_Eq(node.typeName, "") {
StringBuilder_Append(&cbe.sb, node.typeName);
} else {
StringBuilder_Append(&cbe.sb, "int");
}
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, node.strValue);
return;
}
// Store: combine alloca + value into single declaration
if kind == hStore {
if node.child1 != null as *HirNode && node.child1.kind == hAlloca {
// Declaration with initializer: Type x = value;
if !String_Eq(node.child1.typeName, "") {
StringBuilder_Append(&cbe.sb, node.child1.typeName);
} else {
StringBuilder_Append(&cbe.sb, CBackend_TypeToC(node.child1.intValue));
}
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, node.child1.strValue);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// Plain assignment
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// If
if kind == hIf {
StringBuilder_Append(&cbe.sb, "if (");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ") {\n");
if node.child2 != null as *HirNode {
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, node.child2);
cbe.indent = cbe.indent - 1;
}
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}");
let elseBlock: *HirNode = node.extraData as *HirNode;
if elseBlock != null as *HirNode {
StringBuilder_Append(&cbe.sb, " else {\n");
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, elseBlock);
cbe.indent = cbe.indent - 1;
sp = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}\n");
} else {
StringBuilder_Append(&cbe.sb, "\n");
}
return;
}
// While
if kind == hWhile {
StringBuilder_Append(&cbe.sb, "while (");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ") {\n");
if node.child2 != null as *HirNode {
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, node.child2);
cbe.indent = cbe.indent - 1;
}
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}");
return;
}
// Loop (infinite)
if kind == hLoop {
StringBuilder_Append(&cbe.sb, "while (1) {\n");
if node.child1 != null as *HirNode {
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, node.child1);
cbe.indent = cbe.indent - 1;
}
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "}");
return;
}
// Field access: obj.field — emit as obj->field (since most are pointers)
if kind == hFieldPtr {
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, "->");
StringBuilder_Append(&cbe.sb, node.strValue);
return;
}
// Sizeof: sizeof(Type) — emit as sizeof(Type)
if kind == hSizeOf {
StringBuilder_Append(&cbe.sb, "sizeof(");
if !String_Eq(node.typeName, "") {
StringBuilder_Append(&cbe.sb, node.typeName);
} else {
StringBuilder_Append(&cbe.sb, "int");
}
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Index: arr[idx] — emit as arr[idx]
if kind == hIndexPtr {
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, "[");
CBE_EmitExpr(cbe, node.child2);
StringBuilder_Append(&cbe.sb, "]");
return;
}
// Load: *ptr — emit as *ptr (dereference)
if kind == hLoad {
StringBuilder_Append(&cbe.sb, "(*");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Assign: target = value
if kind == hAssign {
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child2);
return;
}
// Cast
if kind == hCast {
StringBuilder_Append(&cbe.sb, "((");
if !String_Eq(node.typeName, "") {
StringBuilder_Append(&cbe.sb, node.typeName);
} else {
StringBuilder_Append(&cbe.sb, CBackend_TypeToC(node.typeKind));
}
StringBuilder_Append(&cbe.sb, ")");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ")");
return;
}
// Struct init: ((TypeName){.field = value, ...})
if kind == hStructInit {
StringBuilder_Append(&cbe.sb, "((");
StringBuilder_Append(&cbe.sb, node.strValue);
StringBuilder_Append(&cbe.sb, "){");
// Emit fields (chained via child3)
var field: *HirNode = node.child1;
var first: bool = true;
while field != null as *HirNode {
if !first {
StringBuilder_Append(&cbe.sb, ", ");
}
StringBuilder_Append(&cbe.sb, ".");
StringBuilder_Append(&cbe.sb, field.strValue);
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, field.child1);
first = false;
field = field.child3;
}
StringBuilder_Append(&cbe.sb, "})");
return;
}
// Block — emit each statement via child3 linked list
if kind == hBlock {
var child: *HirNode = node.child1;
while child != null as *HirNode {
// Indent
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
CBE_EmitExpr(cbe, child);
// Add semicolon for statements that need it
if child.kind != hBlock && child.kind != hIf && child.kind != hWhile && child.kind != hLoop {
StringBuilder_Append(&cbe.sb, ";");
}
StringBuilder_Append(&cbe.sb, "\n");
child = child.child3;
}
return;
}
}
// ---------------------------------------------------------------------------
// Emit function declaration
// ---------------------------------------------------------------------------
func CBE_EmitFuncDecl(cbe: *CEmitter, f: *HirFunc) {
// Return type
if String_Eq(f.retTypeName, "") || String_Eq(f.retTypeName, "void") {
StringBuilder_Append(&cbe.sb, "void ");
} else {
StringBuilder_Append(&cbe.sb, f.retTypeName);
StringBuilder_Append(&cbe.sb, " ");
}
StringBuilder_Append(&cbe.sb, f.name);
StringBuilder_Append(&cbe.sb, "(");
// Parameters
var i: int = 0;
while i < f.paramCount {
if i > 0 {
StringBuilder_Append(&cbe.sb, ", ");
}
// Use param type info
var pname: String = "";
var ptype: String = "";
if i == 0 { pname = f.param0.name; ptype = f.param0.typeName; }
if i == 1 { pname = f.param1.name; ptype = f.param1.typeName; }
if i == 2 { pname = f.param2.name; ptype = f.param2.typeName; }
if i == 3 { pname = f.param3.name; ptype = f.param3.typeName; }
if i == 4 { pname = f.param4.name; ptype = f.param4.typeName; }
if i == 5 { pname = f.param5.name; ptype = f.param5.typeName; }
if i == 6 { pname = f.param6.name; ptype = f.param6.typeName; }
if i == 7 { pname = f.param7.name; ptype = f.param7.typeName; }
if i == 8 { pname = f.param8.name; ptype = f.param8.typeName; }
// Emit type
if String_Eq(ptype, "String") || String_Eq(ptype, "str") {
StringBuilder_Append(&cbe.sb, "String ");
} else if String_Eq(ptype, "bool") {
StringBuilder_Append(&cbe.sb, "bool ");
} else if String_Eq(ptype, "int") {
StringBuilder_Append(&cbe.sb, "int ");
} else if String_Eq(ptype, "int64") {
StringBuilder_Append(&cbe.sb, "int64 ");
} else if String_Eq(ptype, "uint") {
StringBuilder_Append(&cbe.sb, "uint ");
} else if String_Eq(ptype, "float64") {
StringBuilder_Append(&cbe.sb, "float64 ");
} else if !String_Eq(ptype, "") {
StringBuilder_Append(&cbe.sb, ptype);
StringBuilder_Append(&cbe.sb, " ");
} else {
StringBuilder_Append(&cbe.sb, "int "); // fallback
}
StringBuilder_Append(&cbe.sb, pname);
i = i + 1;
}
StringBuilder_Append(&cbe.sb, ")");
}
// ---------------------------------------------------------------------------
// Helpers for detecting generic declarations (not yet monomorphized)
// ---------------------------------------------------------------------------
func CBE_IsGenericTypeName(name: String) -> bool {
if String_Eq(name, "T") || String_Eq(name, "K") || String_Eq(name, "V") { return true; }
if String_Eq(name, "T*") || String_Eq(name, "K*") || String_Eq(name, "V*") { return true; }
// Generic container structs and their pointer variants
if String_Eq(name, "Array") || String_Eq(name, "Array*") { return true; }
if String_Eq(name, "SetEntry") || String_Eq(name, "SetEntry*") { return true; }
if String_Eq(name, "StringMapEntry") || String_Eq(name, "StringMapEntry*") { return true; }
if String_Eq(name, "MapEntry") || String_Eq(name, "MapEntry*") { return true; }
return false;
}
func CBE_StructHasGeneric(st: *HirStruct) -> bool {
var fi: int = 0;
while fi < st.fieldCount {
if CBE_IsGenericTypeName(st.fields[fi].typeName) { return true; }
fi = fi + 1;
}
return false;
}
func CBE_FuncHasGeneric(f: *HirFunc) -> bool {
var pi: int = 0;
while pi < f.paramCount {
var ptype: String = "";
if pi == 0 { ptype = f.param0.typeName; }
if pi == 1 { ptype = f.param1.typeName; }
if pi == 2 { ptype = f.param2.typeName; }
if pi == 3 { ptype = f.param3.typeName; }
if pi == 4 { ptype = f.param4.typeName; }
if pi == 5 { ptype = f.param5.typeName; }
if pi == 6 { ptype = f.param6.typeName; }
if pi == 7 { ptype = f.param7.typeName; }
if pi == 8 { ptype = f.param8.typeName; }
if CBE_IsGenericTypeName(ptype) { return true; }
pi = pi + 1;
}
if CBE_IsGenericTypeName(f.retTypeName) { return true; }
return false;
}
// ---------------------------------------------------------------------------
// Generate complete C module
// ---------------------------------------------------------------------------
func CBackend_Generate(mod: *HirModule) -> String {
let cbe: *CEmitter = bux_alloc(sizeof(CEmitter)) as *CEmitter;
cbe.sb = StringBuilder_NewCap(8192);
cbe.indent = 0;
// Header
StringBuilder_Append(&cbe.sb, "// Generated by Bux C Backend\n");
StringBuilder_Append(&cbe.sb, "#include <stdint.h>\n");
StringBuilder_Append(&cbe.sb, "#include <stdbool.h>\n");
StringBuilder_Append(&cbe.sb, "#include <string.h>\n");
StringBuilder_Append(&cbe.sb, "#include <stdio.h>\n");
StringBuilder_Append(&cbe.sb, "#include <stdlib.h>\n\n");
// Type aliases
StringBuilder_Append(&cbe.sb, "typedef const char* String;\n");
StringBuilder_Append(&cbe.sb, "typedef unsigned char uint8;\n");
StringBuilder_Append(&cbe.sb, "typedef unsigned short uint16;\n");
StringBuilder_Append(&cbe.sb, "typedef unsigned int uint32;\n");
StringBuilder_Append(&cbe.sb, "typedef unsigned long long uint64;\n");
StringBuilder_Append(&cbe.sb, "typedef signed char int8;\n");
StringBuilder_Append(&cbe.sb, "typedef short int16;\n");
StringBuilder_Append(&cbe.sb, "typedef long long int64;\n");
StringBuilder_Append(&cbe.sb, "typedef float float32;\n");
StringBuilder_Append(&cbe.sb, "typedef double float64;\n");
StringBuilder_Append(&cbe.sb, "typedef char char8;\n\n");
// Runtime declarations
StringBuilder_Append(&cbe.sb, "void* bux_alloc(unsigned int size);\n");
StringBuilder_Append(&cbe.sb, "void bux_free(void* ptr);\n\n");
// Forward declare all struct types (skip generics)
var si: int = 0;
while si < mod.structCount {
if !CBE_StructHasGeneric(&mod.structs[si]) {
StringBuilder_Append(&cbe.sb, "typedef struct ");
StringBuilder_Append(&cbe.sb, mod.structs[si].name);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, mod.structs[si].name);
StringBuilder_Append(&cbe.sb, ";\n");
}
si = si + 1;
}
StringBuilder_Append(&cbe.sb, "\n");
// Enum definitions (typedef to int)
var ei: int = 0;
while ei < mod.enumCount {
StringBuilder_Append(&cbe.sb, "typedef int ");
StringBuilder_Append(&cbe.sb, mod.enums[ei].name);
StringBuilder_Append(&cbe.sb, ";\n");
ei = ei + 1;
}
if mod.enumCount > 0 {
StringBuilder_Append(&cbe.sb, "\n");
}
// Constant definitions
var ci: int = 0;
while ci < mod.constCount {
StringBuilder_Append(&cbe.sb, "#define ");
StringBuilder_Append(&cbe.sb, mod.consts[ci].name);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, String_FromInt(mod.consts[ci].value as int64));
StringBuilder_Append(&cbe.sb, "\n");
ci = ci + 1;
}
if mod.constCount > 0 {
StringBuilder_Append(&cbe.sb, "\n");
}
// Struct definitions (skip generics)
si = 0;
while si < mod.structCount {
if CBE_StructHasGeneric(&mod.structs[si]) {
si = si + 1;
continue;
}
StringBuilder_Append(&cbe.sb, "struct ");
StringBuilder_Append(&cbe.sb, mod.structs[si].name);
StringBuilder_Append(&cbe.sb, " {\n");
var fi: int = 0;
while fi < mod.structs[si].fieldCount {
StringBuilder_Append(&cbe.sb, " ");
var ft: String = mod.structs[si].fields[fi].typeName;
if String_Eq(ft, "int") || String_Eq(ft, "") {
StringBuilder_Append(&cbe.sb, "int");
} else if String_Eq(ft, "String") {
StringBuilder_Append(&cbe.sb, "String");
} else if String_Eq(ft, "bool") {
StringBuilder_Append(&cbe.sb, "bool");
} else if String_Eq(ft, "uint32") {
StringBuilder_Append(&cbe.sb, "uint32");
} else {
// Use the type name directly (handles "Foo*", "Bar", etc.)
StringBuilder_Append(&cbe.sb, ft);
}
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, mod.structs[si].fields[fi].name);
StringBuilder_Append(&cbe.sb, ";\n");
fi = fi + 1;
}
StringBuilder_Append(&cbe.sb, "};\n\n");
si = si + 1;
}
// Forward declarations for all functions (skip generics)
var i: int = 0;
while i < mod.funcCount {
if !CBE_FuncHasGeneric(&mod.funcs[i]) {
CBE_EmitFuncDecl(cbe, &mod.funcs[i]);
StringBuilder_Append(&cbe.sb, ";\n");
}
i = i + 1;
}
StringBuilder_Append(&cbe.sb, "\n");
// Extern declarations
i = 0;
while i < mod.externCount {
CBE_EmitFuncDecl(cbe, &mod.externFuncs[i]);
StringBuilder_Append(&cbe.sb, ";\n");
i = i + 1;
}
StringBuilder_Append(&cbe.sb, "\n");
// Function definitions (skip generics)
var hasMain: bool = false;
i = 0;
while i < mod.funcCount {
if String_Eq(mod.funcs[i].name, "Main") { hasMain = true; }
// Skip generic functions
if CBE_FuncHasGeneric(&mod.funcs[i]) {
i = i + 1;
continue;
}
// Skip forward declarations (functions without body)
let body: *HirNode = mod.funcs[i].body;
if body == null as *HirNode {
i = i + 1;
continue;
}
CBE_EmitFuncDecl(cbe, &mod.funcs[i]);
StringBuilder_Append(&cbe.sb, " {\n");
// Body
var hasReturn: bool = false;
cbe.indent = 1;
CBE_EmitExpr(cbe, body);
cbe.indent = 0;
// Check if body has a return statement
var stmt: *HirNode = body.child1;
while stmt != null as *HirNode {
if stmt.kind == hReturn { hasReturn = true; }
stmt = stmt.child3;
}
// Add default return 0 only if function returns int and has no explicit return
if String_Eq(mod.funcs[i].retTypeName, "int") && !hasReturn {
StringBuilder_Append(&cbe.sb, " return 0;\n");
}
StringBuilder_Append(&cbe.sb, "}\n\n");
i = i + 1;
}
// Generate C main wrapper if Main function exists
if hasMain {
StringBuilder_Append(&cbe.sb, "extern int g_argc;\n");
StringBuilder_Append(&cbe.sb, "extern char** g_argv;\n");
StringBuilder_Append(&cbe.sb, "int main(int argc, char** argv) {\n");
StringBuilder_Append(&cbe.sb, " g_argc = argc;\n");
StringBuilder_Append(&cbe.sb, " g_argv = argv;\n");
StringBuilder_Append(&cbe.sb, " return Main();\n");
StringBuilder_Append(&cbe.sb, "}\n");
}
return StringBuilder_Build(&cbe.sb);
}
func CBackend_Free(cbe: *CEmitter) {
StringBuilder_Free(&cbe.sb);
bux_free(cbe as *void);
}
}
+761
View File
@@ -0,0 +1,761 @@
// cli.bux — CLI driver for the Bux self-hosting compiler
// Wires together: Lexer → Parser → Sema → HirLower → CBackend
module Cli {
extern func PrintLine(s: String);
extern func Print(s: String);
extern func bux_read_file(path: String) -> String;
extern func bux_write_file(path: String, content: String) -> bool;
extern func bux_file_exists(path: String) -> int;
extern func bux_dir_exists(path: String) -> int;
extern func bux_path_join(a: String, b: String) -> String;
extern func bux_mkdir_if_needed(path: String) -> int;
extern func bux_run_nim(nim_file: String, out_bin: String) -> int;
extern func bux_list_dir(dir: String, ext: String, out_count: *int) -> *String;
extern func bux_system(cmd: String) -> int;
func ReadFile(path: String) -> String {
return bux_read_file(path);
}
func WriteFile(path: String, content: String) -> bool {
return bux_write_file(path, content);
}
func FileExists(path: String) -> bool {
return bux_file_exists(path) != 0;
}
func DirExists(path: String) -> bool {
return bux_dir_exists(path) != 0;
}
// Import the compiler pipeline
// In self-hosting mode, these are compiled together from compiler/selfhost/
// ---------------------------------------------------------------------------
// Compile a single .bux source file
// ---------------------------------------------------------------------------
func Cli_Compile(source: String, sourceName: String) -> String {
// Phase 1: Lex
PrintLine(" Lexing...");
let lex: *Lexer = Lexer_Tokenize(source);
PrintLine(" Lex done");
if Lexer_DiagCount(lex) > 0 {
PrintLine("Lex errors:");
var i: int = 0;
while i < Lexer_DiagCount(lex) {
Print(" ");
PrintLine(lex.diags[i].message);
i = i + 1;
}
return "";
}
// Phase 2: Parse
PrintLine(" Parsing...");
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module {
PrintLine("Parse failed");
return "";
}
PrintLine(" Parse done");
// Flatten module wrappers: find module decl and hoist its children
var decl: *Decl = mod.firstItem;
while decl != null as *Decl {
if decl.kind == dkModule && decl.childDecl1 != null as *Decl {
mod.firstItem = decl.childDecl1;
break;
}
decl = decl.childDecl2;
}
// Phase 3: Semantic analysis
PrintLine(" Sema...");
let sema: *Sema = Sema_Analyze(mod);
if Sema_HasError(sema) {
PrintLine("Sema errors:");
var i: int = 0;
while i < Sema_DiagCount(sema) {
Print(" ");
PrintLine(sema.diags[i].message);
i = i + 1;
}
return "";
}
PrintLine(" Sema done");
// Phase 4: HIR lowering
PrintLine(" HIR lowering...");
let hirMod: *HirModule = HirLower_LowerModule(mod, sema);
if hirMod == null as *HirModule {
PrintLine("HIR lowering failed");
return "";
}
Print("HIR funcCount=");
PrintInt(hirMod.funcCount);
// Phase 5: C code generation
let cCode: String = CBackend_Generate(hirMod);
// Cleanup
Lexer_Free(lex);
Sema_Free(sema);
return cCode;
}
// ---------------------------------------------------------------------------
// Build command
// ---------------------------------------------------------------------------
func Cli_Build(srcPath: String, outPath: String) -> int {
let source: String = ReadFile(srcPath);
if source == null as String || String_Eq(source, "") || !FileExists(srcPath) {
Print("Error: cannot read source file: ");
PrintLine(srcPath);
return 1;
}
Print("Compiling ");
PrintLine(srcPath);
let cCode: String = Cli_Compile(source, srcPath);
if String_Eq(cCode, "") {
PrintLine("Compilation failed");
return 1;
}
// Write C output
let cFile: String = String_Concat(outPath, ".c");
let ok: bool = WriteFile(cFile, cCode);
if !ok {
PrintLine("Error: cannot write output file");
return 1;
}
Print(" → C written to ");
PrintLine(cFile);
// Find runtime.c and io.c for linking
var rtPath: String = "library/runtime/runtime.c";
var ioPath: String = "library/runtime/io.c";
if !FileExists(rtPath) {
rtPath = "../library/runtime/runtime.c";
}
if !FileExists(ioPath) {
ioPath = "../library/runtime/io.c";
}
if !FileExists(rtPath) {
rtPath = "/home/ziko/z-git/bux/bux/library/runtime/runtime.c";
}
if !FileExists(ioPath) {
ioPath = "/home/ziko/z-git/bux/bux/library/runtime/io.c";
}
// Compile with cc
PrintLine("Compiling C...");
var cmdBuf: StringBuilder = StringBuilder_NewCap(512);
StringBuilder_Append(&cmdBuf, "cc -O2 -pthread -o ");
StringBuilder_Append(&cmdBuf, outPath);
StringBuilder_Append(&cmdBuf, " ");
StringBuilder_Append(&cmdBuf, cFile);
StringBuilder_Append(&cmdBuf, " ");
if FileExists(rtPath) {
StringBuilder_Append(&cmdBuf, rtPath);
StringBuilder_Append(&cmdBuf, " ");
}
if FileExists(ioPath) {
StringBuilder_Append(&cmdBuf, ioPath);
StringBuilder_Append(&cmdBuf, " ");
}
StringBuilder_Append(&cmdBuf, "-lm");
let ccRc: int = bux_system(StringBuilder_Build(&cmdBuf));
if ccRc != 0 {
PrintLine("Error: C compilation failed");
return 1;
}
Print(" → Binary: ");
PrintLine(outPath);
return 0;
}
// ---------------------------------------------------------------------------
// Check command (compile only, no output)
// ---------------------------------------------------------------------------
func Cli_Check(srcPath: String) -> int {
Print("Check: ");
PrintLine(srcPath);
let source: String = ReadFile(srcPath);
if source == null as String || String_Eq(source, "") {
PrintLine("Error: cannot read source file");
return 1;
}
PrintLine(" File read OK");
// Phase 1: Lex
PrintLine(" Lexing...");
let lex: *Lexer = Lexer_Tokenize(source);
PrintLine(" Lex done");
if Lexer_DiagCount(lex) > 0 {
PrintLine("Lex errors found");
return 1;
}
// Phase 2: Parse
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module {
PrintLine("Parse failed");
return 1;
}
PrintLine(" Parse done");
// Flatten module wrappers
var decl2: *Decl = mod.firstItem;
while decl2 != null as *Decl {
if decl2.kind == dkModule && decl2.childDecl1 != null as *Decl {
mod.firstItem = decl2.childDecl1;
break;
}
decl2 = decl2.childDecl2;
}
// Phase 3: Sema
let sema: *Sema = Sema_Analyze(mod);
if Sema_HasError(sema) {
PrintLine("Sema errors found");
var i: int = 0;
while i < Sema_DiagCount(sema) {
Print(" line ");
PrintInt(sema.diags[i].line as int64);
Print(": ");
PrintLine(sema.diags[i].message);
i = i + 1;
}
return 1;
}
// Phase 4: HIR lowering
let hirMod: *HirModule = HirLower_LowerModule(mod, sema);
var dc: *Decl = mod.firstItem;
while dc != null as *Decl {
if dc.kind == dkFunc {
Print("check func ");
PrintLine(dc.strValue);
}
dc = dc.childDecl2;
}
PrintLine("Check passed");
return 0;
}
// ---------------------------------------------------------------------------
// Project build — compile all .bux files in src/ directory
// ---------------------------------------------------------------------------
func Cli_CompileSource(source: String, sourceName: String) -> *HirModule {
// Phase 1: Lex
PrintLine(" Lexing...");
let lex: *Lexer = Lexer_Tokenize(source);
PrintLine(" Lex done");
if Lexer_DiagCount(lex) > 0 {
Print("Lex errors in ");
PrintLine(sourceName);
return null as *HirModule;
}
// Phase 2: Parse
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module {
Print("Parse failed for ");
PrintLine(sourceName);
return null as *HirModule;
}
// Phase 3: Semantic analysis
let sema: *Sema = Sema_Analyze(mod);
if Sema_HasError(sema) {
Print("Sema errors in ");
PrintLine(sourceName);
return null as *HirModule;
}
// Phase 4: HIR lowering
let hirMod: *HirModule = HirLower_LowerModule(mod, sema);
return hirMod;
}
// ---------------------------------------------------------------------------
// Stdlib discovery and merging
// ---------------------------------------------------------------------------
func Cli_FindStdlibDir(projectDir: String) -> String {
var path: String = bux_path_join(projectDir, "stdlib");
if DirExists(path) { return path; }
path = bux_path_join(projectDir, "../stdlib");
if DirExists(path) { return path; }
path = "/home/ziko/z-git/bux/bux/stdlib";
if DirExists(path) { return path; }
return "";
}
func Cli_DeclName(decl: *Decl) -> String {
if decl == null as *Decl { return ""; }
return decl.strValue;
}
func Cli_NameInList(name: String, names: *String, count: int) -> bool {
if String_Eq(name, "") { return false; }
var i: int = 0;
while i < count {
if String_Eq(names[i], name) {
return true;
}
i = i + 1;
}
return false;
}
func Cli_CollectNames(mod: *Module, names: *String, maxCount: int) -> int {
if mod == null as *Module { return 0; }
var count: int = 0;
var decl: *Decl = mod.firstItem;
while decl != null as *Decl && count < maxCount {
let next: *Decl = decl.childDecl2;
if decl.kind == dkModule {
var inner: *Decl = decl.childDecl1;
while inner != null as *Decl && count < maxCount {
let iname: String = Cli_DeclName(inner);
if !String_Eq(iname, "") {
names[count] = iname;
count = count + 1;
}
inner = inner.childDecl2;
}
} else {
let dname: String = Cli_DeclName(decl);
if !String_Eq(dname, "") {
names[count] = dname;
count = count + 1;
}
}
decl = next;
}
return count;
}
// Helper: convert single char int to String
func String_FromChar(c: int) -> String {
var buf: *char8 = bux_alloc(2) as *char8;
buf[0] = c as char8;
buf[1] = 0 as char8;
return buf as String;
}
// Collect stdlib module paths imported by user code.
// Returns number of unique stdlib files to merge (paths written into outPaths).
func Cli_CollectStdlibImports(mod: *Module, outPaths: *String, maxCount: int, stdlibDir: String) -> int {
if mod == null as *Module { return 0; }
var count: int = 0;
var decl: *Decl = mod.firstItem;
while decl != null as *Decl && count < maxCount {
if decl.kind == dkUse {
let path: String = decl.usePath;
// Only handle Std::* imports
if String_StartsWith(path, "Std::") {
// Extract module name after Std:: (e.g., "Std::Io" -> "Io")
var j: int = 5; // skip "Std::"
var modName: String = "";
while j < 100 {
let c: int = path[j] as int;
if c == 0 || c == 58 { break; } // null or ':'
modName = String_Concat(modName, String_FromChar(c));
j = j + 1;
}
if !String_Eq(modName, "") {
let filePath: String = bux_path_join(bux_path_join(stdlibDir, "Std"), String_Concat(modName, ".bux"));
// Check for duplicates
var dup: bool = false;
var di: int = 0;
while di < count {
if String_Eq(outPaths[di], filePath) { dup = true; }
di = di + 1;
}
if !dup {
outPaths[count] = filePath;
count = count + 1;
}
}
}
}
decl = decl.childDecl2;
}
return count;
}
func Cli_MergeFileInto(target: *Module, path: String, skipNames: *String, skipCount: int) -> int {
if !FileExists(path) {
Print("Error: stdlib file not found: ");
PrintLine(path);
return 0;
}
let source: String = ReadFile(path);
if source == null as String || String_Eq(source, "") { return 0; }
let lex: *Lexer = Lexer_Tokenize(source);
if Lexer_DiagCount(lex) > 0 { return 0; }
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module { return 0; }
var added: int = 0;
var decl: *Decl = mod.firstItem;
while decl != null as *Decl {
let next: *Decl = decl.childDecl2;
decl.childDecl2 = null as *Decl;
if decl.kind == dkModule {
var inner: *Decl = decl.childDecl1;
while inner != null as *Decl {
let innerNext: *Decl = inner.childDecl2;
let iname: String = Cli_DeclName(inner);
if !String_Eq(iname, "") && Cli_NameInList(iname, skipNames, skipCount) {
// Skip shadowed stdlib decl
} else {
inner.childDecl2 = target.firstItem;
target.firstItem = inner;
target.itemCount = target.itemCount + 1;
added = added + 1;
}
inner = innerNext;
}
} else {
let dname: String = Cli_DeclName(decl);
if !String_Eq(dname, "") && Cli_NameInList(dname, skipNames, skipCount) {
// Skip shadowed stdlib decl
} else {
decl.childDecl2 = target.firstItem;
target.firstItem = decl;
target.itemCount = target.itemCount + 1;
added = added + 1;
}
}
decl = next;
}
return added;
}
func Cli_CopyModuleDecls(target: *Module, source: *Module) {
if source == null as *Module || source.firstItem == null as *Decl { return; }
var decl: *Decl = source.firstItem;
var limit: int = 0;
while decl != null as *Decl && limit < 10000 {
let next: *Decl = decl.childDecl2;
decl.childDecl2 = target.firstItem;
target.firstItem = decl;
target.itemCount = target.itemCount + 1;
decl = next;
limit = limit + 1;
}
source.firstItem = null as *Decl;
source.itemCount = 0;
}
func Cli_BuildProject(projectDir: String) -> int {
let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
var outName: String = man.name;
if String_Eq(outName, "") {
outName = "bux_out";
}
let srcDir: String = bux_path_join(projectDir, "src");
if !DirExists(srcDir) {
Print("Error: no src/ directory in ");
PrintLine(projectDir);
return 1;
}
Print("Scanning ");
PrintLine(srcDir);
// List all .bux files in src/
var fileCount: int = 0;
let files: *String = bux_list_dir(srcDir, ".bux", &fileCount);
if fileCount == 0 {
PrintLine("Error: no .bux files found in src/");
return 1;
}
Print("Found ");
PrintInt(fileCount);
PrintLine(" source files");
// Create user merged module (collect user decls first)
let userMerged: *Module = bux_alloc(sizeof(Module)) as *Module;
userMerged.name = "main";
userMerged.path = "";
userMerged.itemCount = 0;
userMerged.firstItem = null as *Decl;
// Parse each file and merge declarations into userMerged module
var i: int = 0;
while i < fileCount {
let path: String = files[i];
Print(" Parsing ");
PrintLine(path);
let source: String = ReadFile(path);
if String_Eq(source, "") {
Print(" Error: cannot read ");
PrintLine(path);
return 1;
}
let lex: *Lexer = Lexer_Tokenize(source);
if Lexer_DiagCount(lex) > 0 {
Print(" Lex errors in ");
PrintLine(path);
return 1;
}
let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
if mod == null as *Module {
Print(" Parse failed for ");
PrintLine(path);
return 1;
}
// Merge declarations from this module into userMerged
var decl: *Decl = mod.firstItem;
while decl != null as *Decl {
let next: *Decl = decl.childDecl2;
decl.childDecl2 = null as *Decl;
// Flatten module declarations
if decl.kind == dkModule {
var inner: *Decl = decl.childDecl1;
while inner != null as *Decl {
let innerNext: *Decl = inner.childDecl2;
inner.childDecl2 = userMerged.firstItem;
userMerged.firstItem = inner;
userMerged.itemCount = userMerged.itemCount + 1;
inner = innerNext;
}
} else {
decl.childDecl2 = userMerged.firstItem;
userMerged.firstItem = decl;
userMerged.itemCount = userMerged.itemCount + 1;
}
decl = next;
}
i = i + 1;
}
// Collect user declaration names for shadow detection
let maxNames: int = 2048;
let userNames: *String = bux_alloc(maxNames * 8) as *String;
let userNameCount: int = Cli_CollectNames(userMerged, userNames, maxNames);
// Create final merged module
let merged: *Module = bux_alloc(sizeof(Module)) as *Module;
merged.name = "main";
merged.path = "";
merged.itemCount = 0;
merged.firstItem = null as *Decl;
// Find and merge stdlib declarations (only imported modules)
let stdlibDir: String = Cli_FindStdlibDir(projectDir);
if !String_Eq(stdlibDir, "") {
Print("Stdlib found: ");
PrintLine(stdlibDir);
// Collect imported stdlib modules from user code
let maxImports: int = 64;
let importPaths: *String = bux_alloc(maxImports * 8) as *String;
let importCount: int = Cli_CollectStdlibImports(userMerged, importPaths, maxImports, stdlibDir);
Print("Imported stdlib modules: ");
PrintInt(importCount);
PrintLine("");
if importCount > 0 {
var si: int = 0;
var stdAdded: int = 0;
while si < importCount {
Print(" Merging ");
PrintLine(importPaths[si]);
let added: int = Cli_MergeFileInto(merged, importPaths[si], userNames, userNameCount);
stdAdded = stdAdded + added;
si = si + 1;
}
Print("Stdlib declarations added: ");
PrintInt(stdAdded);
}
}
// Copy user declarations into merged (user shadows stdlib)
Cli_CopyModuleDecls(merged, userMerged);
Print("Merged ");
PrintInt(merged.itemCount);
PrintLine(" declarations");
// Semantic analysis
PrintLine("Running sema...");
let sema: *Sema = Sema_Analyze(merged);
if Sema_HasError(sema) {
PrintLine("Sema errors found");
var i: int = 0;
while i < Sema_DiagCount(sema) {
Print(" line ");
PrintInt(sema.diags[i].line as int64);
Print(": ");
PrintLine(sema.diags[i].message);
i = i + 1;
}
return 1;
}
// HIR lowering
PrintLine("Lowering to HIR...");
let hirMod: *HirModule = HirLower_LowerModule(merged, sema);
// C code generation
PrintLine("Generating C code...");
let cCode: String = CBackend_Generate(hirMod);
// Create build directory
let buildDir: String = bux_path_join(projectDir, "build");
discard bux_mkdir_if_needed(buildDir);
// Write C output
let cFile: String = bux_path_join(buildDir, "main.c");
if !WriteFile(cFile, cCode) {
PrintLine("Error: cannot write main.c");
return 1;
}
Print("C code written to ");
PrintLine(cFile);
// Find runtime.c and io.c (use stdlibDir if available)
var rtPath: String = bux_path_join(stdlibDir, "runtime.c");
var ioPath: String = bux_path_join(stdlibDir, "io.c");
if !FileExists(rtPath) {
rtPath = bux_path_join(projectDir, "library/runtime/runtime.c");
}
if !FileExists(ioPath) {
ioPath = bux_path_join(projectDir, "library/runtime/io.c");
}
if !FileExists(rtPath) {
rtPath = bux_path_join(projectDir, "../library/runtime/runtime.c");
}
if !FileExists(ioPath) {
ioPath = bux_path_join(projectDir, "../library/runtime/io.c");
}
// Compile with cc
PrintLine("Compiling C...");
let outBin: String = bux_path_join(buildDir, outName);
var ccBuf: StringBuilder = StringBuilder_NewCap(512);
StringBuilder_Append(&ccBuf, "cc -O2 -pthread -o ");
StringBuilder_Append(&ccBuf, outBin);
StringBuilder_Append(&ccBuf, " ");
StringBuilder_Append(&ccBuf, cFile);
StringBuilder_Append(&ccBuf, " ");
if FileExists(rtPath) {
StringBuilder_Append(&ccBuf, rtPath);
StringBuilder_Append(&ccBuf, " ");
}
if FileExists(ioPath) {
StringBuilder_Append(&ccBuf, ioPath);
StringBuilder_Append(&ccBuf, " ");
}
StringBuilder_Append(&ccBuf, "-lm");
let ccRc: int = bux_system(StringBuilder_Build(&ccBuf));
if ccRc != 0 {
PrintLine("Error: C compilation failed");
return 1;
}
Print("Build successful: ");
PrintLine(outBin);
return 0;
}
// ---------------------------------------------------------------------------
// Run command — build project and execute
// ---------------------------------------------------------------------------
func Cli_RunProject(projectDir: String) -> int {
let rc: int = Cli_BuildProject(projectDir);
if rc != 0 {
return rc;
}
let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
var outName: String = man.name;
if String_Eq(outName, "") {
outName = "bux_out";
}
let outBin: String = bux_path_join(bux_path_join(projectDir, "build"), outName);
Print("Running: ");
PrintLine(outBin);
return bux_system(outBin);
}
// ---------------------------------------------------------------------------
// Main entry — dispatch based on args
// ---------------------------------------------------------------------------
func Cli_Run(args: *String, argCount: int) -> int {
if argCount < 2 {
PrintLine("Bux Self-Hosting Compiler v0.2.0");
PrintLine("Usage: buxc <command> [args]");
PrintLine("Commands: build, check, run, project, help, version");
return 0;
}
let cmd: String = args[1];
if String_Eq(cmd, "version") || String_Eq(cmd, "--version") || String_Eq(cmd, "-v") {
PrintLine("Bux 0.2.0 (self-hosting bootstrap)");
return 0;
}
if String_Eq(cmd, "help") || String_Eq(cmd, "--help") || String_Eq(cmd, "-h") {
PrintLine("Bux Self-Hosting Compiler v0.2.0");
PrintLine("Usage: buxc <command> [args]");
PrintLine("Commands: build, check, run, project, help, version");
PrintLine("Pipeline modules:");
PrintLine(" Lexer ✅ 695 lines");
PrintLine(" Parser ✅ 1004 lines");
PrintLine(" Sema ✅ 393 lines");
PrintLine(" HirLower ✅ 307 lines");
PrintLine(" CBackend ✅ 585 lines");
PrintLine(" Total: 3830 lines of Bux");
return 0;
}
if String_Eq(cmd, "check") {
if argCount < 3 {
PrintLine("Usage: buxc check <file.bux>");
return 1;
}
return Cli_Check(args[2]);
}
if String_Eq(cmd, "build") {
let src: String = "src/Main.bux";
let out: String = "build/main";
if argCount >= 3 { src = args[2]; }
if argCount >= 4 { out = args[3]; }
return Cli_Build(src, out);
}
if String_Eq(cmd, "project") {
let dir: String = ".";
if argCount >= 3 { dir = args[2]; }
return Cli_BuildProject(dir);
}
if String_Eq(cmd, "run") {
let dir: String = ".";
if argCount >= 3 { dir = args[2]; }
return Cli_RunProject(dir);
}
Print("Unknown command: ");
PrintLine(cmd);
return 1;
}
}
+229
View File
@@ -0,0 +1,229 @@
// hir.bux — HIR (High-level Intermediate Representation) node types
module Hir {
// HIR node kinds
const hLit: int = 0;
const hVar: int = 1;
const hSelf: int = 2;
const hUnary: int = 3;
const hBinary: int = 4;
const hAssign: int = 5;
const hIf: int = 6;
const hWhile: int = 7;
const hLoop: int = 8;
const hBreak: int = 9;
const hContinue: int = 10;
const hReturn: int = 11;
const hAlloca: int = 12;
const hLoad: int = 13;
const hStore: int = 14;
const hFieldPtr: int = 15;
const hArrowField: int = 16;
const hIndexPtr: int = 17;
const hCall: int = 18;
const hCallIndirect: int = 19;
const hCast: int = 20;
const hIs: int = 21;
const hSizeOf: int = 22;
const hBlock: int = 23;
const hStructInit: int = 24;
const hSliceInit: int = 25;
const hRange: int = 26;
const hTupleInit: int = 27;
const hMatch: int = 28;
const hSpawn: int = 29;
const hAwait: int = 30;
// ---------------------------------------------------------------------------
// HirArgList — linked list for call arguments beyond 2
// ---------------------------------------------------------------------------
struct HirArgList {
node: *HirNode,
next: *HirArgList,
}
// ---------------------------------------------------------------------------
// HirNode — unified struct with tagged union pattern
// ---------------------------------------------------------------------------
struct HirNode {
kind: int;
line: uint32;
column: uint32;
typeKind: int;
typeName: String;
// Common fields (used by multiple kinds)
strValue: String; // var name; callee name; field name; label
intValue: int; // token kind (for lit; unary op; binary op)
boolValue: bool; // range inclusive; isScope
// Child nodes (up to 3)
child1: *HirNode; // left/operand/condition/base
child2: *HirNode; // right/value/then/body
child3: *HirNode; // else/third
// Extra data pointer (for children arrays, field lists, etc.)
extraData: *void;
extraCount: int;
}
// ---------------------------------------------------------------------------
// HirFunc
// ---------------------------------------------------------------------------
struct HirParam {
name: String;
typeKind: int;
typeName: String;
}
struct HirFunc {
name: String;
paramCount: int;
param0: HirParam;
param1: HirParam;
param2: HirParam;
param3: HirParam;
param4: HirParam;
param5: HirParam;
param6: HirParam;
param7: HirParam;
param8: HirParam;
retTypeKind: int;
retTypeName: String;
body: *HirNode;
isPublic: bool;
}
// ---------------------------------------------------------------------------
// HirEnumVariant
// ---------------------------------------------------------------------------
struct HirEnumVariant {
name: String;
fieldCount: int;
fieldType0: int;
fieldName0: String;
fieldType1: int;
fieldName1: String;
}
// ---------------------------------------------------------------------------
// HirModule
// ---------------------------------------------------------------------------
struct HirStructField {
name: String;
typeName: String;
}
struct HirStruct {
name: String;
fieldCount: int;
fields: *HirStructField;
}
struct HirConst {
name: String;
value: int;
}
struct HirEnum {
name: String;
}
struct HirModule {
funcCount: int;
funcs: *HirFunc;
externCount: int;
externFuncs: *HirFunc;
structCount: int;
structs: *HirStruct;
enumCount: int;
enums: *HirEnum;
constCount: int;
consts: *HirConst;
}
// ---------------------------------------------------------------------------
// Constructor helpers
// ---------------------------------------------------------------------------
func Hir_MakeNode(kind: int, line: uint32, column: uint32) -> HirNode {
return HirNode { kind: kind, line: line, column: column,
typeKind: 0, typeName: "",
strValue: "", intValue: 0, boolValue: false,
child1: null as *HirNode, child2: null as *HirNode, child3: null as *HirNode,
extraData: null as *void, extraCount: 0 };
}
func Hir_MakeLit(tokKind: int, tokText: String, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hLit, line, col);
n.intValue = tokKind;
n.strValue = tokText;
return n;
}
func Hir_MakeVar(name: String, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hVar, line, col);
n.strValue = name;
return n;
}
func Hir_MakeBinary(op: int, left: *HirNode, right: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hBinary, line, col);
n.intValue = op;
n.child1 = left;
n.child2 = right;
return n;
}
func Hir_MakeCall(callee: String, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hCall, line, col);
n.strValue = callee;
return n;
}
func Hir_MakeReturn(value: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hReturn, line, col);
n.child1 = value;
return n;
}
func Hir_MakeBlock(line: uint32, col: uint32) -> HirNode {
return Hir_MakeNode(hBlock, line, col);
}
func Hir_MakeIf(cond: *HirNode, thenBody: *HirNode, elseBody: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hIf, line, col);
n.child1 = cond;
n.child2 = thenBody;
n.child3 = elseBody;
return n;
}
func Hir_MakeWhile(cond: *HirNode, body: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hWhile, line, col);
n.child1 = cond;
n.child2 = body;
return n;
}
func Hir_MakeAlloca(name: String, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hAlloca, line, col);
n.strValue = name;
return n;
}
func Hir_MakeLoad(ptr: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hLoad, line, col);
n.child1 = ptr;
return n;
}
func Hir_MakeStore(ptr: *HirNode, value: *HirNode, line: uint32, col: uint32) -> HirNode {
var n: HirNode = Hir_MakeNode(hStore, line, col);
n.child1 = ptr;
n.child2 = value;
return n;
}
}
+740
View File
@@ -0,0 +1,740 @@
// hir_lower.bux — HIR lowering: AST → HIR transformation (ported from hir_lower.nim)
// Transforms the typed AST into a lower-level IR suitable for code generation.
module HirLower {
// ---------------------------------------------------------------------------
// Lowering context
// ---------------------------------------------------------------------------
struct LowerCtx {
module: *Module,
scope: *Scope,
funcs: *HirFunc,
funcCount: int,
externFuncs: *HirFunc,
externCount: int,
varCounter: int,
tryCounter: int,
}
// ---------------------------------------------------------------------------
// TypeExpr.kind → Type.kind resolver
// TypeExpr.kind values (0-5) overlap with Type.kind values — this
// resolves the correct Type.kind for codegen.
// ---------------------------------------------------------------------------
func Lcx_ResolveTypeKind(te: *TypeExpr) -> int {
if te == null as *TypeExpr { return tyUnknown; }
let name: String = te.typeName;
if te.kind == tekPointer { return tyPointer; }
if te.kind == tekSlice { return tySlice; }
if te.kind == tekTuple { return tyTuple; }
// Named types: resolve by name
if String_Eq(name, "void") { return tyVoid; }
if String_Eq(name, "bool") { return tyBool; }
if String_Eq(name, "bool8") { return tyBool8; }
if String_Eq(name, "bool16") { return tyBool16; }
if String_Eq(name, "bool32") { return tyBool32; }
if String_Eq(name, "char8") { return tyChar8; }
if String_Eq(name, "char16") { return tyChar16; }
if String_Eq(name, "char32") { return tyChar32; }
if String_Eq(name, "String") { return tyStr; }
if String_Eq(name, "str") { return tyStr; }
if String_Eq(name, "int8") { return tyInt8; }
if String_Eq(name, "int16") { return tyInt16; }
if String_Eq(name, "int32") { return tyInt32; }
if String_Eq(name, "int64") { return tyInt64; }
if String_Eq(name, "int") { return tyInt; }
if String_Eq(name, "uint8") { return tyUInt8; }
if String_Eq(name, "uint16") { return tyUInt16; }
if String_Eq(name, "uint32") { return tyUInt32; }
if String_Eq(name, "uint64") { return tyUInt64; }
if String_Eq(name, "uint") { return tyUInt; }
if String_Eq(name, "float32") { return tyFloat32; }
if String_Eq(name, "float64") { return tyFloat64; }
if String_Eq(name, "float") { return tyFloat64; }
return tyNamed;
}
// ---------------------------------------------------------------------------
// Fresh name generation
// ---------------------------------------------------------------------------
func Lcx_FreshName(ctx: *LowerCtx) -> String {
ctx.varCounter = ctx.varCounter + 1;
return String_FromInt(ctx.varCounter as int64);
}
// ---------------------------------------------------------------------------
// Type → HIR type
// ---------------------------------------------------------------------------
func Lcx_LowerType(typeKind: int, typeName: String) -> int {
return typeKind;
}
// ---------------------------------------------------------------------------
// Expression lowering
// ---------------------------------------------------------------------------
func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
if expr == null as *Expr { return null as *HirNode; }
let line: uint32 = expr.line;
let col: uint32 = expr.column;
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
n.kind = hBlock;
n.line = line;
n.column = col;
let kind: int = expr.kind;
// Literal
if kind == ekLiteral {
n.kind = hLit;
n.intValue = expr.tokKind;
n.strValue = expr.tokText;
return n;
}
// Identifier → variable reference
if kind == ekIdent {
n.kind = hVar;
n.strValue = expr.strValue;
let sym: Symbol = Scope_Lookup(ctx.scope, expr.strValue);
n.typeKind = sym.typeKind;
if expr.refType != null as *TypeExpr {
n.typeName = expr.refType.typeName;
}
if sym.typeName != null as String && !String_Eq(sym.typeName, "") {
n.typeName = sym.typeName;
}
return n;
}
// self → variable reference named "self"
if kind == ekSelf {
n.kind = hVar;
n.strValue = "self";
let sym: Symbol = Scope_Lookup(ctx.scope, "self");
n.typeKind = sym.typeKind;
if sym.typeName != null as String && !String_Eq(sym.typeName, "") {
n.typeName = sym.typeName;
}
return n;
}
// Binary
if kind == ekBinary {
// Assignment operator → use hAssign
if expr.intValue == tkAssign {
n.kind = hAssign;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
return n;
}
n.kind = hBinary;
n.intValue = expr.intValue; // operator
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
return n;
}
// Unary
if kind == ekUnary {
n.kind = hUnary;
n.intValue = expr.intValue;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
return n;
}
// Call
if kind == ekCall {
n.kind = hCall;
// Method call desugaring: obj.method(args) → Type_method(obj, args)
if expr.child1 != null as *Expr && expr.child1.kind == ekField {
let methodName: String = expr.child1.strValue;
var receiverTypeName: String = "";
if expr.child1.child1 != null as *Expr && expr.child1.child1.kind == ekIdent {
let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.child1.strValue);
receiverTypeName = sym.typeName;
}
if String_Eq(receiverTypeName, "") && expr.child1.child1 != null as *Expr && expr.child1.child1.refType != null as *TypeExpr {
receiverTypeName = expr.child1.child1.refType.typeName;
}
if !String_Eq(receiverTypeName, "") {
// Strip trailing '*' from pointer type names (e.g. "Box*" → "Box")
var baseName: String = receiverTypeName;
let len: int = bux_strlen(baseName) as int;
if len > 0 {
let lastChar: String = bux_str_slice(baseName, (len - 1) as uint, 1);
if String_Eq(lastChar, "*") {
baseName = bux_str_slice(baseName, 0, (len - 1) as uint);
}
}
n.strValue = String_Concat(baseName, "_");
n.strValue = String_Concat(n.strValue, methodName);
}
// Lower receiver as first argument
let recv: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1);
n.child1 = recv;
// Lower remaining arguments from linked list
var arg: *ExprList = expr.callArgs;
var argIdx: int = 0;
while arg != null as *ExprList {
let lowered: *HirNode = Lcx_LowerExpr(ctx, arg.expr);
if argIdx == 0 {
n.child2 = lowered;
} else if argIdx == 1 {
// Third argument — start linked list
let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
firstExtra.node = lowered;
firstExtra.next = null as *HirArgList;
n.extraData = firstExtra as *void;
n.extraCount = 1;
} else {
// Additional args — append to linked list
var cur: *HirArgList = n.extraData as *HirArgList;
while cur.next != null as *HirArgList {
cur = cur.next;
}
let newNode: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
newNode.node = lowered;
newNode.next = null as *HirArgList;
cur.next = newNode;
n.extraCount = n.extraCount + 1;
}
arg = arg.next;
argIdx = argIdx + 1;
}
return n;
}
// Get callee name
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
n.strValue = expr.child1.strValue;
}
// Lower arguments from linked list
var arg: *ExprList = expr.callArgs;
var argIdx: int = 0;
while arg != null as *ExprList {
let lowered: *HirNode = Lcx_LowerExpr(ctx, arg.expr);
if argIdx == 0 {
n.child1 = lowered;
} else if argIdx == 1 {
n.child2 = lowered;
} else if argIdx == 2 {
// Third argument — start linked list
let firstExtra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
firstExtra.node = lowered;
firstExtra.next = null as *HirArgList;
n.extraData = firstExtra as *void;
n.extraCount = 1;
} else {
// Additional args — append to linked list
var cur: *HirArgList = n.extraData as *HirArgList;
while cur.next != null as *HirArgList {
cur = cur.next;
}
let newNode: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
newNode.node = lowered;
newNode.next = null as *HirArgList;
cur.next = newNode;
n.extraCount = n.extraCount + 1;
}
arg = arg.next;
argIdx = argIdx + 1;
}
return n;
}
// Sizeof
if kind == ekSizeOf {
n.kind = hSizeOf;
if expr.refType != null as *TypeExpr {
n.typeName = expr.refType.typeName;
}
return n;
}
// Field access
if kind == ekField {
// Check if this is enum variant access: Color::Green
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.strValue);
if sym.decl != null as *Decl && sym.decl.kind == dkEnum {
// Emit as variable reference: Color_Green
n.kind = hVar;
n.strValue = String_Concat(String_Concat(expr.child1.strValue, "_"), expr.strValue);
return n;
}
}
n.kind = hFieldPtr;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
n.strValue = expr.strValue;
// Get struct type from base expr refType
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
n.typeName = expr.child1.refType.typeName;
}
return n;
}
// spawn Callee(args)
if kind == ekSpawn {
n.kind = hSpawn;
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
n.strValue = expr.child1.strValue;
}
if expr.child2 != null as *Expr {
n.child1 = Lcx_LowerExpr(ctx, expr.child2);
}
return n;
}
// expr.await
if kind == ekAwait {
n.kind = hAwait;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
return n;
}
// Index: arr[idx]
if kind == ekIndex {
n.kind = hIndexPtr;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
return n;
}
// Assign: target = value
if kind == ekAssign {
n.kind = hAssign;
n.child1 = Lcx_LowerExpr(ctx, expr.child1); // target
n.child2 = Lcx_LowerExpr(ctx, expr.child2); // value
return n;
}
// Cast
if kind == ekCast {
n.kind = hCast;
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
if expr.refType != null as *TypeExpr {
let resolvedKind: int = Lcx_ResolveTypeKind(expr.refType);
n.typeKind = resolvedKind;
// For pointer types, construct "PointeeType*"
if expr.refType.kind == tekPointer && expr.refType.pointerPointee != null as *TypeExpr {
n.typeName = String_Concat(expr.refType.pointerPointee.typeName, "*");
} else if !String_Eq(expr.refType.typeName, "") {
n.typeName = expr.refType.typeName;
}
}
return n;
}
// Struct init: TypeName { field: value, ... }
if kind == ekStructInit {
n.kind = hStructInit;
n.strValue = expr.structName;
// Lower each field (fields are chained via child3 on synthetic ekField exprs)
var field: *Expr = expr.child1;
var firstField: *HirNode = null as *HirNode;
var lastField: *HirNode = null as *HirNode;
while field != null as *Expr {
let fNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fNode.kind = hBlock; // placeholder, field is identified by name+value
fNode.line = expr.line;
fNode.column = expr.column;
fNode.strValue = field.strValue; // field name
fNode.child1 = Lcx_LowerExpr(ctx, field.child1); // field value
if firstField == null as *HirNode {
firstField = fNode;
lastField = fNode;
} else {
lastField.child3 = fNode;
lastField = fNode;
}
field = field.child3;
}
n.child1 = firstField;
return n;
}
return n;
}
// ---------------------------------------------------------------------------
// Statement lowering
// ---------------------------------------------------------------------------
func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
if stmt == null as *Stmt { return null as *HirNode; }
let line: uint32 = stmt.line;
let col: uint32 = stmt.column;
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
n.kind = hBlock;
n.line = line;
n.column = col;
let kind: int = stmt.kind;
// Let/var → alloca + store
if kind == skLet {
let init: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
// alloca for the variable
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
alloca.kind = hAlloca;
alloca.line = line;
alloca.column = col;
alloca.strValue = stmt.strValue;
// Set type from the declared type expression
alloca.typeName = "";
if stmt.refStmtType != null as *TypeExpr {
alloca.intValue = stmt.refStmtType.kind;
alloca.typeKind = Lcx_ResolveTypeKind(stmt.refStmtType);
// For pointer types, construct "PointeeType*"
if stmt.refStmtType.kind == tekPointer && stmt.refStmtType.pointerPointee != null as *TypeExpr {
alloca.typeName = String_Concat(stmt.refStmtType.pointerPointee.typeName, "*");
} else if !String_Eq(stmt.refStmtType.typeName, "") {
alloca.typeName = stmt.refStmtType.typeName;
}
}
// Add to scope for field offset lookups
var sym: Symbol;
sym.kind = skVar;
sym.name = stmt.strValue;
sym.typeKind = alloca.typeKind;
sym.typeName = alloca.typeName;
sym.isMutable = false;
sym.isPublic = false;
sym.decl = null as *Decl;
discard Scope_Define(ctx.scope, sym);
// store the init value
n.kind = hStore;
n.child1 = alloca;
n.child2 = init;
return n;
}
// Return
if kind == skReturn {
n.kind = hReturn;
if stmt.child1 != null as *Expr {
n.child1 = Lcx_LowerExpr(ctx, stmt.child1);
}
return n;
}
// Expression statement
if kind == skExpr && stmt.child1 != null as *Expr {
return Lcx_LowerExpr(ctx, stmt.child1);
}
// If
if kind == skIf {
n.kind = hIf;
n.child1 = Lcx_LowerExpr(ctx, stmt.child1); // condition
if stmt.refStmtBlock != null as *Block {
n.child2 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1);
}
if stmt.refStmtElse != null as *Block {
// Store else block in extraData (child3 is reserved for block chaining)
n.extraData = Lcx_LowerBlock(ctx, stmt.refStmtElse, -1) as *void;
}
return n;
}
// While
if kind == skWhile {
n.kind = hWhile;
n.child1 = Lcx_LowerExpr(ctx, stmt.child1);
if stmt.refStmtBlock != null as *Block {
n.child2 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1);
}
return n;
}
// Loop
if kind == skLoop {
n.kind = hLoop;
if stmt.refStmtBlock != null as *Block {
n.child1 = Lcx_LowerBlock(ctx, stmt.refStmtBlock, -1);
}
return n;
}
return n;
}
// ---------------------------------------------------------------------------
// Block lowering
// ---------------------------------------------------------------------------
func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode {
if block == null as *Block { return null as *HirNode; }
if block.stmtCount == 0 { return null as *HirNode; }
// Build a linked list of HirNodes via child3:
// node1 (stmt1) → child3 → node2 (stmt2) → child3 → node3 (stmt3) → null
// child3 is safe for chaining because:
// hStore: child1=alloca, child2=value, child3 unused
// hReturn: child1=value, child2/child3 unused
// hCall: child1=arg1, child2=arg2, child3 unused
var firstNode: *HirNode = null as *HirNode;
var prevNode: *HirNode = null as *HirNode;
var stmt: *Stmt = block.firstStmt;
while stmt != null as *Stmt {
let lowered: *HirNode = Lcx_LowerStmt(ctx, stmt);
if lowered != null as *HirNode {
if firstNode == null as *HirNode {
firstNode = lowered;
prevNode = lowered;
} else {
prevNode.child3 = lowered;
prevNode = lowered;
}
}
stmt = stmt.nextStmt;
}
// Wrap in an hBlock node with child1 = first statement in chain
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
n.kind = hBlock;
n.line = block.line;
n.column = block.column;
n.boolValue = true;
n.child1 = firstNode;
return n;
}
// ---------------------------------------------------------------------------
// Param → HirParam conversion
// ---------------------------------------------------------------------------
func Lcx_LowerParam(out: *HirParam, p: *Param) {
out.name = p.name;
if p.refParamType != null as *TypeExpr {
out.typeKind = Lcx_ResolveTypeKind(p.refParamType);
// Use typeName directly if set (parser may have constructed "String*")
if !String_Eq(p.refParamType.typeName, "") {
out.typeName = p.refParamType.typeName;
} else if p.refParamType.kind == tekPointer && p.refParamType.pointerPointee != null as *TypeExpr {
out.typeName = String_Concat(p.refParamType.pointerPointee.typeName, "*");
} else {
out.typeName = "";
}
} else {
out.typeKind = 0;
out.typeName = "";
}
}
// ---------------------------------------------------------------------------
// Function lowering
// ---------------------------------------------------------------------------
func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
let f: *HirFunc = bux_alloc(sizeof(HirFunc)) as *HirFunc;
f.name = decl.strValue;
f.isPublic = decl.isPublic;
f.paramCount = decl.paramCount;
Lcx_LowerParam(&f.param0, &decl.param0);
Lcx_LowerParam(&f.param1, &decl.param1);
Lcx_LowerParam(&f.param2, &decl.param2);
Lcx_LowerParam(&f.param3, &decl.param3);
Lcx_LowerParam(&f.param4, &decl.param4);
Lcx_LowerParam(&f.param5, &decl.param5);
Lcx_LowerParam(&f.param6, &decl.param6);
Lcx_LowerParam(&f.param7, &decl.param7);
Lcx_LowerParam(&f.param8, &decl.param8);
if decl.retType != null as *TypeExpr {
f.retTypeName = decl.retType.typeName;
f.retTypeKind = Lcx_ResolveTypeKind(decl.retType);
} else {
f.retTypeName = "";
f.retTypeKind = 0;
}
// Add parameters to hir_lower scope for field offset lookups
var pi: int = 0;
while pi < decl.paramCount {
var p: *Param = null as *Param;
if pi == 0 { p = &decl.param0; }
else if pi == 1 { p = &decl.param1; }
else if pi == 2 { p = &decl.param2; }
else if pi == 3 { p = &decl.param3; }
else if pi == 4 { p = &decl.param4; }
else if pi == 5 { p = &decl.param5; }
else if pi == 6 { p = &decl.param6; }
else if pi == 7 { p = &decl.param7; }
else if pi == 8 { p = &decl.param8; }
if p != null as *Param && p.refParamType != null as *TypeExpr {
var sym: Symbol;
sym.kind = skVar;
sym.name = p.name;
sym.typeKind = Lcx_ResolveTypeKind(p.refParamType);
sym.typeName = p.refParamType.typeName;
sym.isMutable = false;
sym.isPublic = false;
sym.decl = null as *Decl;
discard Scope_Define(ctx.scope, sym);
}
pi = pi + 1;
}
if decl.refBody != null as *Block {
f.body = Lcx_LowerBlock(ctx, decl.refBody, -1);
} else {
f.body = null as *HirNode;
}
return f;
}
// ---------------------------------------------------------------------------
// Module lowering — main entry point
// ---------------------------------------------------------------------------
func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
let ctx: *LowerCtx = bux_alloc(sizeof(LowerCtx)) as *LowerCtx;
ctx.module = mod;
ctx.scope = sema.scope;
ctx.funcs = bux_alloc(256 as uint * sizeof(HirFunc)) as *HirFunc;
ctx.funcCount = 0;
ctx.externFuncs = bux_alloc(256 as uint * sizeof(HirFunc)) as *HirFunc;
ctx.externCount = 0;
ctx.varCounter = 0;
let hm: *HirModule = bux_alloc(sizeof(HirModule)) as *HirModule;
hm.funcCount = 0;
hm.funcs = ctx.funcs;
hm.structCount = 0;
hm.structs = bux_alloc(64 as uint * sizeof(HirStruct)) as *HirStruct;
hm.enumCount = 0;
hm.enums = bux_alloc(64 as uint * sizeof(HirEnum)) as *HirEnum;
hm.constCount = 0;
hm.consts = bux_alloc(512 as uint * sizeof(HirConst)) as *HirConst;
// First pass: count structs (to allocate field arrays later)
// Second pass: actually collect them
// For simplicity, do single pass with pre-allocated field arrays
// Iterate declarations
var decl: *Decl = mod.firstItem;
while decl != null as *Decl {
if decl.kind == dkFunc {
let f: *HirFunc = Lcx_LowerFunc(ctx, decl);
ctx.funcs[ctx.funcCount] = *f;
ctx.funcCount = ctx.funcCount + 1;
}
if decl.kind == dkImpl {
let implTypeName: String = decl.strValue;
var implDecl: *Decl = decl.childDecl1;
while implDecl != null as *Decl {
if implDecl.kind == dkFunc {
let mangled: String = String_Concat(implTypeName, "_");
implDecl.strValue = String_Concat(mangled, implDecl.strValue);
let f: *HirFunc = Lcx_LowerFunc(ctx, implDecl);
ctx.funcs[ctx.funcCount] = *f;
ctx.funcCount = ctx.funcCount + 1;
}
implDecl = implDecl.childDecl2;
}
}
if decl.kind == dkExternFunc {
let f: *HirFunc = Lcx_LowerFunc(ctx, decl);
ctx.externFuncs[ctx.externCount] = *f;
ctx.externCount = ctx.externCount + 1;
}
if decl.kind == dkStruct {
// Collect struct definition for C codegen
let si: int = hm.structCount;
hm.structs[si].name = decl.strValue;
hm.structs[si].fieldCount = decl.fieldCount;
hm.structs[si].fields = bux_alloc(decl.fieldCount as uint * sizeof(HirStructField)) as *HirStructField;
var fi: int = 0;
while fi < decl.fieldCount {
var fname: String = "";
var ftype: *TypeExpr = null as *TypeExpr;
if fi == 0 { fname = decl.field0.name; ftype = decl.field0.refFieldType; }
else if fi == 1 { fname = decl.field1.name; ftype = decl.field1.refFieldType; }
else if fi == 2 { fname = decl.field2.name; ftype = decl.field2.refFieldType; }
else if fi == 3 { fname = decl.field3.name; ftype = decl.field3.refFieldType; }
else if fi == 4 { fname = decl.field4.name; ftype = decl.field4.refFieldType; }
else if fi == 5 { fname = decl.field5.name; ftype = decl.field5.refFieldType; }
else if fi == 6 { fname = decl.field6.name; ftype = decl.field6.refFieldType; }
else if fi == 7 { fname = decl.field7.name; ftype = decl.field7.refFieldType; }
// Skip empty field names
if String_Eq(fname, "") {
fi = fi + 1;
continue;
}
hm.structs[si].fields[fi].name = fname;
if ftype != null as *TypeExpr {
if ftype.kind == tekPointer && ftype.pointerPointee != null as *TypeExpr {
// Pointer type: emit "TypeName*"
if !String_Eq(ftype.pointerPointee.typeName, "") {
hm.structs[si].fields[fi].typeName = String_Concat(ftype.pointerPointee.typeName, "*");
}
} else if !String_Eq(ftype.typeName, "") {
hm.structs[si].fields[fi].typeName = ftype.typeName;
}
}
fi = fi + 1;
}
hm.structCount = hm.structCount + 1;
}
if decl.kind == dkConst && hm.constCount < 512 {
let ci: int = hm.constCount;
hm.consts[ci].name = decl.strValue;
var val: int = 0;
if decl.constValue != null as *Expr {
if decl.constValue.kind == ekLiteral {
val = decl.constValue.intValue;
}
}
hm.consts[ci].value = val;
hm.constCount = hm.constCount + 1;
}
if decl.kind == dkEnum {
let ei: int = hm.enumCount;
hm.enums[ei].name = decl.strValue;
hm.enumCount = hm.enumCount + 1;
// Emit enum variants as constants: EnumName_VariantName = value
var vi: int = 0;
while vi < decl.variantCount && hm.constCount < 512 {
var vname: String = "";
if vi == 0 { vname = decl.variant0.name; }
if vi == 1 { vname = decl.variant1.name; }
if vi == 2 { vname = decl.variant2.name; }
if vi == 3 { vname = decl.variant3.name; }
if !String_Eq(vname, "") {
let ci: int = hm.constCount;
hm.consts[ci].name = String_Concat(String_Concat(decl.strValue, "_"), vname);
hm.consts[ci].value = vi;
hm.constCount = hm.constCount + 1;
}
vi = vi + 1;
}
}
decl = decl.childDecl2;
}
hm.funcCount = ctx.funcCount;
hm.funcs = ctx.funcs;
hm.externCount = ctx.externCount;
hm.externFuncs = ctx.externFuncs;
return hm;
}
func HirLower_Free(ctx: *LowerCtx) {
bux_free(ctx.funcs as *void);
bux_free(ctx.externFuncs as *void);
bux_free(ctx as *void);
}
}
+816
View File
@@ -0,0 +1,816 @@
// lexer.bux — Lexer state machine (ported from lexer.nim)
// Tokenizes Bux source into a stream of tokens.
module Lexer {
extern func bux_strlen(s: String) -> uint;
// ---------------------------------------------------------------------------
// Character helpers (wrap C ctype)
// ---------------------------------------------------------------------------
func Lex_IsDigit(c: uint32) -> bool {
return c >= 48 && c <= 57; // '0'..'9'
}
func Lex_IsHexDigit(c: uint32) -> bool {
if c >= 48 && c <= 57 { return true; } // 0-9
if c >= 65 && c <= 70 { return true; } // A-F
if c >= 97 && c <= 102 { return true; } // a-f
return false;
}
func Lex_IsBinDigit(c: uint32) -> bool {
return c == 48 || c == 49; // '0' or '1'
}
func Lex_IsOctDigit(c: uint32) -> bool {
return c >= 48 && c <= 55; // '0'..'7'
}
func Lex_IsIdentStart(c: uint32) -> bool {
if c >= 97 && c <= 122 { return true; } // a-z
if c >= 65 && c <= 90 { return true; } // A-Z
return c == 95; // '_'
}
func Lex_IsIdentChar(c: uint32) -> bool {
if Lex_IsIdentStart(c) { return true; }
return Lex_IsDigit(c);
}
// ---------------------------------------------------------------------------
// Lexer state
// ---------------------------------------------------------------------------
const maxTokens: int = 32768;
const maxDiags: int = 128;
struct LexerDiag {
line: uint32;
column: uint32;
message: String;
}
struct Lexer {
source: String;
sourceLen: int;
pos: int;
line: uint32;
column: uint32;
startLine: uint32;
startColumn: uint32;
startPos: int;
tokenCount: int;
tokens: *LexToken;
diagCount: int;
diags: *LexerDiag;
}
struct LexToken {
kind: int;
text: String;
line: uint32;
column: uint32;
}
// ---------------------------------------------------------------------------
// Init
// ---------------------------------------------------------------------------
func Lexer_Init(source: String) -> Lexer {
let len: uint = bux_strlen(source);
let tokBuf: *LexToken = bux_alloc(maxTokens as uint * sizeof(LexToken)) as *LexToken;
let diagBuf: *LexerDiag = bux_alloc(maxDiags as uint * sizeof(LexerDiag)) as *LexerDiag;
return Lexer {
source: source, sourceLen: len as int,
pos: 0, line: 1, column: 1,
startLine: 0, startColumn: 0, startPos: 0,
tokenCount: 0, tokens: tokBuf,
diagCount: 0, diags: diagBuf
};
}
// ---------------------------------------------------------------------------
// Core primitives
// ---------------------------------------------------------------------------
func lexIsAtEnd(lex: *Lexer) -> bool {
return lex.pos >= lex.sourceLen;
}
func lexPeek(lex: *Lexer, ahead: int) -> uint32 {
let i: int = lex.pos + ahead;
if i < lex.sourceLen {
return (lex.source[i] as uint32) & 255;
}
return 0;
}
func lexAdvance(lex: *Lexer) -> uint32 {
let c: uint32 = lexPeek(lex, 0);
if !lexIsAtEnd(lex) {
lex.pos = lex.pos + 1;
if c == 10 { // '\n'
lex.line = lex.line + 1;
lex.column = 1;
} else {
lex.column = lex.column + 1;
}
}
return c;
}
func lexMatch(lex: *Lexer, expected: uint32) -> bool {
if lexIsAtEnd(lex) { return false; }
if lexPeek(lex, 0) != expected { return false; }
discard lexAdvance(lex);
return true;
}
func lexMatchStr(lex: *Lexer, s: String) -> bool {
let len: uint = bux_strlen(s);
var i: int = 0;
while i < (len as int) {
if lexPeek(lex, i) != (s[i] as uint32) {
return false;
}
i = i + 1;
}
i = 0;
while i < (len as int) {
discard lexAdvance(lex);
i = i + 1;
}
return true;
}
// ---------------------------------------------------------------------------
// Token emission
// ---------------------------------------------------------------------------
func lexMarkStart(lex: *Lexer) {
lex.startLine = lex.line;
lex.startColumn = lex.column;
lex.startPos = lex.pos;
}
func lexMakeToken(lex: *Lexer, kind: int) -> LexToken {
var text: String = "";
let endPos: int = lex.pos;
let startPos: int = lex.startPos;
if endPos > startPos {
let len: int = endPos - startPos;
let buf: *char8 = bux_alloc((len + 1) as uint) as *char8;
var i: int = 0;
while i < len {
buf[i] = lex.source[startPos + i] as char8;
i = i + 1;
}
buf[len] = 0 as char8;
text = buf;
} else {
text = "";
}
return LexToken {
kind: kind, text: text,
line: lex.startLine, column: lex.startColumn
};
}
func lexEmitToken(lex: *Lexer, kind: int) {
let tok: LexToken = lexMakeToken(lex, kind);
if lex.tokenCount < maxTokens {
lex.tokens[lex.tokenCount] = tok;
lex.tokenCount = lex.tokenCount + 1;
}
}
func lexSetLastTokenText(lex: *Lexer, text: String) {
if lex.tokenCount > 0 {
lex.tokens[lex.tokenCount - 1].text = text;
}
}
func lexEmitDiag(lex: *Lexer, msg: String) {
if lex.diagCount < maxDiags {
lex.diags[lex.diagCount] = LexerDiag {
line: lex.line, column: lex.column, message: msg
};
lex.diagCount = lex.diagCount + 1;
}
}
// ---------------------------------------------------------------------------
// Whitespace / comments
// ---------------------------------------------------------------------------
func lexSkipLineComment(lex: *Lexer) {
while !lexIsAtEnd(lex) && lexPeek(lex, 0) != 10 { // '\n'
discard lexAdvance(lex);
}
}
func lexSkipBlockComment(lex: *Lexer) {
var depth: int = 1;
while !lexIsAtEnd(lex) && depth > 0 {
if lexPeek(lex, 0) == 47 && lexPeek(lex, 1) == 42 { // /*
discard lexAdvance(lex);
discard lexAdvance(lex);
depth = depth + 1;
} else if lexPeek(lex, 0) == 42 && lexPeek(lex, 1) == 47 { // */
discard lexAdvance(lex);
discard lexAdvance(lex);
depth = depth - 1;
} else {
discard lexAdvance(lex);
}
}
if depth > 0 {
lexEmitDiag(lex, "unterminated block comment");
}
}
func lexSkipWhitespace(lex: *Lexer) {
while !lexIsAtEnd(lex) {
let c: uint32 = lexPeek(lex, 0);
if c == 32 || c == 9 || c == 13 {
discard lexAdvance(lex);
} else {
if c == 47 && lexPeek(lex, 1) == 47 {
lexSkipLineComment(lex);
} else {
if c == 47 && lexPeek(lex, 1) == 42 {
discard lexAdvance(lex);
discard lexAdvance(lex);
lexSkipBlockComment(lex);
} else {
break;
}
}
}
}
}
// ---------------------------------------------------------------------------
// Identifiers
// ---------------------------------------------------------------------------
func lexIsIdentKind(tok: int) -> bool {
return tok == tkFunc || tok == tkLet || tok == tkVar || tok == tkConst
|| tok == tkType || tok == tkStruct || tok == tkEnum || tok == tkUnion
|| tok == tkInterface || tok == tkExtend || tok == tkModule || tok == tkImport
|| tok == tkPub || tok == tkExtern || tok == tkIf || tok == tkElse
|| tok == tkWhile || tok == tkDo || tok == tkLoop || tok == tkFor
|| tok == tkIn || tok == tkBreak || tok == tkContinue || tok == tkReturn
|| tok == tkMatch || tok == tkAs || tok == tkIs || tok == tkNull
|| tok == tkSelf || tok == tkSuper;
}
func lexKeywordKind(text: String) -> int {
if String_Eq(text, "true") { return tkBoolLiteral; }
if String_Eq(text, "false") { return tkBoolLiteral; }
if String_Eq(text, "func") { return tkFunc; }
if String_Eq(text, "let") { return tkLet; }
if String_Eq(text, "var") { return tkVar; }
if String_Eq(text, "const") { return tkConst; }
if String_Eq(text, "type") { return tkType; }
if String_Eq(text, "struct") { return tkStruct; }
if String_Eq(text, "enum") { return tkEnum; }
if String_Eq(text, "union") { return tkUnion; }
if String_Eq(text, "interface") { return tkInterface; }
if String_Eq(text, "extend") { return tkExtend; }
if String_Eq(text, "module") { return tkModule; }
if String_Eq(text, "import") { return tkImport; }
if String_Eq(text, "pub") { return tkPub; }
if String_Eq(text, "extern") { return tkExtern; }
if String_Eq(text, "if") { return tkIf; }
if String_Eq(text, "else") { return tkElse; }
if String_Eq(text, "while") { return tkWhile; }
if String_Eq(text, "do") { return tkDo; }
if String_Eq(text, "loop") { return tkLoop; }
if String_Eq(text, "for") { return tkFor; }
if String_Eq(text, "in") { return tkIn; }
if String_Eq(text, "break") { return tkBreak; }
if String_Eq(text, "continue") { return tkContinue; }
if String_Eq(text, "return") { return tkReturn; }
if String_Eq(text, "match") { return tkMatch; }
if String_Eq(text, "as") { return tkAs; }
if String_Eq(text, "is") { return tkIs; }
if String_Eq(text, "null") { return tkNull; }
if String_Eq(text, "self") { return tkSelf; }
if String_Eq(text, "super") { return tkSuper; }
if String_Eq(text, "sizeof") { return tkSizeOf; }
if String_Eq(text, "discard") { return tkDiscard; }
if String_Eq(text, "async") { return tkAsync; }
if String_Eq(text, "await") { return tkAwait; }
if String_Eq(text, "spawn") { return tkSpawn; }
return tkIdent;
}
func lexScanIdent(lex: *Lexer) {
lexMarkStart(lex);
while !lexIsAtEnd(lex) && Lex_IsIdentChar(lexPeek(lex, 0)) {
discard lexAdvance(lex);
}
let tok: LexToken = lexMakeToken(lex, tkIdent);
let kind: int = lexKeywordKind(tok.text);
tok.kind = kind;
if lex.tokenCount < maxTokens {
lex.tokens[lex.tokenCount] = tok;
lex.tokenCount = lex.tokenCount + 1;
}
}
// ---------------------------------------------------------------------------
// Numbers
// ---------------------------------------------------------------------------
func lexScanDigits(lex: *Lexer) {
while !lexIsAtEnd(lex) && Lex_IsDigit(lexPeek(lex, 0)) {
discard lexAdvance(lex);
}
}
func lexScanHexDigits(lex: *Lexer) {
while !lexIsAtEnd(lex) && Lex_IsHexDigit(lexPeek(lex, 0)) {
discard lexAdvance(lex);
}
}
func lexScanNumber(lex: *Lexer) {
lexMarkStart(lex);
var isFloat: bool = false;
if lexPeek(lex, 0) == 48 { // '0'
let p1: uint32 = lexPeek(lex, 1);
if p1 == 120 || p1 == 88 || p1 == 98 || p1 == 66 || p1 == 111 || p1 == 79 { // x X b B o O
discard lexAdvance(lex); // 0
discard lexAdvance(lex); // prefix
if p1 == 120 || p1 == 88 { lexScanHexDigits(lex); }
else if p1 == 98 || p1 == 66 {
while !lexIsAtEnd(lex) && Lex_IsBinDigit(lexPeek(lex, 0)) {
discard lexAdvance(lex);
}
}
else {
while !lexIsAtEnd(lex) && Lex_IsOctDigit(lexPeek(lex, 0)) {
discard lexAdvance(lex);
}
}
lexEmitToken(lex, tkIntLiteral);
return;
}
}
lexScanDigits(lex);
if lexPeek(lex, 0) == 46 && Lex_IsDigit(lexPeek(lex, 1)) { // . digit
isFloat = true;
discard lexAdvance(lex); // .
lexScanDigits(lex);
}
let e: uint32 = lexPeek(lex, 0);
if e == 101 || e == 69 { // e E
isFloat = true;
discard lexAdvance(lex);
let sign: uint32 = lexPeek(lex, 0);
if sign == 43 || sign == 45 { discard lexAdvance(lex); } // + -
lexScanDigits(lex);
}
if isFloat {
lexEmitToken(lex, tkFloatLiteral);
} else {
lexEmitToken(lex, tkIntLiteral);
}
}
// ---------------------------------------------------------------------------
// Strings and chars
// ---------------------------------------------------------------------------
func lexScanBacktickString(lex: *Lexer) {
lexMarkStart(lex);
if lexPeek(lex, 0) == 96 { discard lexAdvance(lex); } // opening backtick
while !lexIsAtEnd(lex) && lexPeek(lex, 0) != 96 {
discard lexAdvance(lex);
}
if lexIsAtEnd(lex) {
lexEmitDiag(lex, "unterminated backtick string literal");
} else {
discard lexAdvance(lex); // closing backtick
}
lexEmitToken(lex, tkStringLiteral);
}
func lexScanString(lex: *Lexer) {
lexMarkStart(lex);
// Collect the prefix (before opening quote) for the token text
var prefix: String = "";
var prefixLen: int = 0;
if lex.pos > lex.startPos {
let plen: int = lex.pos - lex.startPos;
let pbuf: *char8 = bux_alloc((plen + 1) as uint) as *char8;
var pi: int = 0;
while pi < plen {
pbuf[pi] = lex.source[lex.startPos + pi] as char8;
pi = pi + 1;
}
pbuf[plen] = 0 as char8;
prefix = pbuf;
prefixLen = plen;
}
// Allocate buffer for resolved content (max = remaining source length)
let maxLen: int = 4096;
let resolved: *char8 = bux_alloc(maxLen as uint) as *char8;
var rpos: int = 0;
if lexPeek(lex, 0) == 34 { discard lexAdvance(lex); } // opening "
while !lexIsAtEnd(lex) && lexPeek(lex, 0) != 34 { // closing "
if lexPeek(lex, 0) == 10 { // \n
lexEmitDiag(lex, "unterminated string literal");
break;
}
if lexPeek(lex, 0) == 92 { // backslash
discard lexAdvance(lex);
if !lexIsAtEnd(lex) {
let ec: uint32 = lexAdvance(lex);
var rc: char8 = ec as char8;
if ec == 110 { rc = 10 as char8; } // \n
else if ec == 114 { rc = 13 as char8; } // \r
else if ec == 116 { rc = 9 as char8; } // \t
else if ec == 48 { rc = 0 as char8; } // \0
else if ec == 92 { rc = 92 as char8; } // \\
else if ec == 34 { rc = 34 as char8; } // \"
else if ec == 39 { rc = 39 as char8; } // \'
resolved[rpos] = rc;
rpos = rpos + 1;
}
} else {
let c: uint32 = lexAdvance(lex);
resolved[rpos] = c as char8;
rpos = rpos + 1;
}
}
resolved[rpos] = 0 as char8;
if lexIsAtEnd(lex) {
lexEmitDiag(lex, "unterminated string literal");
} else {
discard lexAdvance(lex); // closing "
}
lexEmitToken(lex, tkStringLiteral);
// Replace token text with resolved version: prefix + " + resolved + "
let totalLen: int = prefixLen + 1 + rpos + 1;
let finalBuf: *char8 = bux_alloc((totalLen + 1) as uint) as *char8;
var fi: int = 0;
var pi2: int = 0;
while pi2 < prefixLen { finalBuf[fi] = prefix[pi2] as char8; fi = fi + 1; pi2 = pi2 + 1; }
finalBuf[fi] = 34 as char8; fi = fi + 1; // opening "
var ri: int = 0;
while ri < rpos { finalBuf[fi] = resolved[ri]; fi = fi + 1; ri = ri + 1; }
finalBuf[fi] = 34 as char8; fi = fi + 1; // closing "
finalBuf[fi] = 0 as char8;
lexSetLastTokenText(lex, finalBuf);
}
func lexScanChar(lex: *Lexer) {
lexMarkStart(lex);
// Collect the prefix for the token text
var prefix: String = "";
var prefixLen: int = 0;
if lex.pos > lex.startPos {
let plen: int = lex.pos - lex.startPos;
let pbuf: *char8 = bux_alloc((plen + 1) as uint) as *char8;
var pi: int = 0;
while pi < plen {
pbuf[pi] = lex.source[lex.startPos + pi] as char8;
pi = pi + 1;
}
pbuf[plen] = 0 as char8;
prefix = pbuf;
prefixLen = plen;
}
var resolved: char8 = 0 as char8;
if lexPeek(lex, 0) == 39 { discard lexAdvance(lex); } // opening '
if lexIsAtEnd(lex) {
lexEmitDiag(lex, "unterminated char literal");
lexEmitToken(lex, tkCharLiteral);
return;
}
if lexPeek(lex, 0) == 10 { // \n
lexEmitDiag(lex, "newline in char literal");
} else if lexPeek(lex, 0) == 92 { // backslash
discard lexAdvance(lex);
if !lexIsAtEnd(lex) {
let ec: uint32 = lexAdvance(lex);
if ec == 110 { resolved = 10 as char8; } // \n
else if ec == 114 { resolved = 13 as char8; } // \r
else if ec == 116 { resolved = 9 as char8; } // \t
else if ec == 48 { resolved = 0 as char8; } // \0
else if ec == 92 { resolved = 92 as char8; } // \\
else if ec == 34 { resolved = 34 as char8; } // \"
else if ec == 39 { resolved = 39 as char8; } // \'
else { resolved = ec as char8; }
}
} else {
resolved = lexAdvance(lex) as char8;
}
if lexIsAtEnd(lex) || lexPeek(lex, 0) != 39 {
lexEmitDiag(lex, "expected closing ' for char literal");
} else {
discard lexAdvance(lex); // closing '
}
lexEmitToken(lex, tkCharLiteral);
// Replace token text with resolved version
let totalLen: int = prefixLen + 1 + 1 + 1;
let finalBuf: *char8 = bux_alloc((totalLen + 1) as uint) as *char8;
var fi: int = 0;
var pi2: int = 0;
while pi2 < prefixLen { finalBuf[fi] = prefix[pi2] as char8; fi = fi + 1; pi2 = pi2 + 1; }
finalBuf[fi] = 39 as char8; fi = fi + 1; // opening '
finalBuf[fi] = resolved; fi = fi + 1; // resolved char
finalBuf[fi] = 39 as char8; fi = fi + 1; // closing '
finalBuf[fi] = 0 as char8;
lexSetLastTokenText(lex, finalBuf);
}
// ---------------------------------------------------------------------------
// Symbols / operators
// ---------------------------------------------------------------------------
func lexScanSymbol(lex: *Lexer) {
lexMarkStart(lex);
let c: uint32 = lexAdvance(lex);
// Single-char tokens
if c == 40 { lexEmitToken(lex, tkLParen); return; }
if c == 41 { lexEmitToken(lex, tkRParen); return; }
if c == 123 { lexEmitToken(lex, tkLBrace); return; }
if c == 125 { lexEmitToken(lex, tkRBrace); return; }
if c == 91 { lexEmitToken(lex, tkLBracket); return; }
if c == 93 { lexEmitToken(lex, tkRBracket); return; }
if c == 44 { lexEmitToken(lex, tkComma); return; }
if c == 59 { lexEmitToken(lex, tkSemicolon); return; }
if c == 64 { lexEmitToken(lex, tkAt); return; }
if c == 63 { lexEmitToken(lex, tkQuestion); return; }
if c == 126 { lexEmitToken(lex, tkTilde); return; }
// : ::
if c == 58 {
if lexMatch(lex, 58) { lexEmitToken(lex, tkColonColon); }
else { lexEmitToken(lex, tkColon); }
return;
}
// . .. ... ..=
if c == 46 {
if lexPeek(lex, 0) == 46 && lexPeek(lex, 1) == 46 {
discard lexAdvance(lex); discard lexAdvance(lex);
lexEmitToken(lex, tkDotDotDot); return;
}
if lexPeek(lex, 0) == 46 && lexPeek(lex, 1) == 61 {
discard lexAdvance(lex); discard lexAdvance(lex);
lexEmitToken(lex, tkDotDotEqual); return;
}
if lexMatch(lex, 46) { lexEmitToken(lex, tkDotDot); return; }
lexEmitToken(lex, tkDot); return;
}
// - -> -- -=
if c == 45 {
if lexMatch(lex, 62) { lexEmitToken(lex, tkArrow); return; }
if lexMatch(lex, 45) { lexEmitToken(lex, tkMinusMinus); return; }
if lexMatch(lex, 61) { lexEmitToken(lex, tkMinusAssign); return; }
lexEmitToken(lex, tkMinus); return;
}
// + ++ +=
if c == 43 {
if lexMatch(lex, 43) { lexEmitToken(lex, tkPlusPlus); return; }
if lexMatch(lex, 61) { lexEmitToken(lex, tkPlusAssign); return; }
lexEmitToken(lex, tkPlus); return;
}
// * ** *=
if c == 42 {
if lexMatch(lex, 42) { lexEmitToken(lex, tkStarStar); return; }
if lexMatch(lex, 61) { lexEmitToken(lex, tkStarAssign); return; }
lexEmitToken(lex, tkStar); return;
}
// / /=
if c == 47 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkSlashAssign); return; }
lexEmitToken(lex, tkSlash); return;
}
// % %=
if c == 37 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkPercentAssign); return; }
lexEmitToken(lex, tkPercent); return;
}
// = == =>
if c == 61 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkEq); return; }
if lexMatch(lex, 62) { lexEmitToken(lex, tkFatArrow); return; }
lexEmitToken(lex, tkAssign); return;
}
// ! !=
if c == 33 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkNe); return; }
lexEmitToken(lex, tkBang); return;
}
// < <= << <<=
if c == 60 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkLe); return; }
if lexMatch(lex, 60) {
if lexMatch(lex, 61) { lexEmitToken(lex, tkShlAssign); return; }
lexEmitToken(lex, tkShl); return;
}
lexEmitToken(lex, tkLt); return;
}
// > >= >> >>=
if c == 62 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkGe); return; }
if lexMatch(lex, 62) {
if lexMatch(lex, 61) { lexEmitToken(lex, tkShrAssign); return; }
lexEmitToken(lex, tkShr); return;
}
lexEmitToken(lex, tkGt); return;
}
// & && &=
if c == 38 {
if lexMatch(lex, 38) { lexEmitToken(lex, tkAmpAmp); return; }
if lexMatch(lex, 61) { lexEmitToken(lex, tkAmpAssign); return; }
lexEmitToken(lex, tkAmp); return;
}
// | || |=
if c == 124 {
if lexMatch(lex, 124) { lexEmitToken(lex, tkPipePipe); return; }
if lexMatch(lex, 61) { lexEmitToken(lex, tkPipeAssign); return; }
lexEmitToken(lex, tkPipe); return;
}
// ^ ^=
if c == 94 {
if lexMatch(lex, 61) { lexEmitToken(lex, tkCaretAssign); return; }
lexEmitToken(lex, tkCaret); return;
}
// # intrinsics
if c == 35 {
if lexMatchStr(lex, "line") { lexEmitToken(lex, tkHashLine); return; }
if lexMatchStr(lex, "column") { lexEmitToken(lex, tkHashColumn); return; }
if lexMatchStr(lex, "file") { lexEmitToken(lex, tkHashFile); return; }
if lexMatchStr(lex, "function") { lexEmitToken(lex, tkHashFunction); return; }
if lexMatchStr(lex, "date") { lexEmitToken(lex, tkHashDate); return; }
if lexMatchStr(lex, "time") { lexEmitToken(lex, tkHashTime); return; }
if lexMatchStr(lex, "module") { lexEmitToken(lex, tkHashModule); return; }
lexEmitToken(lex, tkHash); return;
}
lexEmitDiag(lex, "unexpected character");
lexEmitToken(lex, tkUnknown);
}
// ---------------------------------------------------------------------------
// Next token
// ---------------------------------------------------------------------------
func lexNextToken(lex: *Lexer) {
lexSkipWhitespace(lex);
if lexIsAtEnd(lex) {
lexMarkStart(lex);
lexEmitToken(lex, tkEndOfFile);
return;
}
let c: uint32 = lexPeek(lex, 0);
if c == 10 { // \n
lexMarkStart(lex);
discard lexAdvance(lex);
lexEmitToken(lex, tkNewLine);
return;
}
// String prefixes: c8" c16" c32"
if c == 99 { // 'c'
let d: uint32 = lexPeek(lex, 1);
if d == 56 && lexPeek(lex, 2) == 34 { // c8"
discard lexAdvance(lex); discard lexAdvance(lex); // c 8
lexScanString(lex); return;
}
if d == 49 && lexPeek(lex, 2) == 54 && lexPeek(lex, 3) == 34 { // c16"
discard lexAdvance(lex); discard lexAdvance(lex); discard lexAdvance(lex);
lexScanString(lex); return;
}
if d == 51 && lexPeek(lex, 2) == 50 && lexPeek(lex, 3) == 34 { // c32"
discard lexAdvance(lex); discard lexAdvance(lex); discard lexAdvance(lex);
lexScanString(lex); return;
}
}
if c == 34 { // "
lexScanString(lex); return;
}
// Backtick raw string
if c == 96 { // backtick
lexScanBacktickString(lex); return;
}
// Char prefixes: c8' c16' c32'
if c == 99 { // 'c'
let d: uint32 = lexPeek(lex, 1);
if d == 56 && lexPeek(lex, 2) == 39 { // c8'
discard lexAdvance(lex); discard lexAdvance(lex);
lexScanChar(lex); return;
}
if d == 49 && lexPeek(lex, 2) == 54 && lexPeek(lex, 3) == 39 { // c16'
discard lexAdvance(lex); discard lexAdvance(lex); discard lexAdvance(lex);
lexScanChar(lex); return;
}
if d == 51 && lexPeek(lex, 2) == 50 && lexPeek(lex, 3) == 39 { // c32'
discard lexAdvance(lex); discard lexAdvance(lex); discard lexAdvance(lex);
lexScanChar(lex); return;
}
}
if c == 39 { // '
lexScanChar(lex); return;
}
if Lex_IsIdentStart(c) {
lexScanIdent(lex); return;
}
if Lex_IsDigit(c) {
lexScanNumber(lex); return;
}
lexScanSymbol(lex);
}
// ---------------------------------------------------------------------------
// Tokenize — main entry point
// ---------------------------------------------------------------------------
func Lexer_Tokenize(source: String) -> *Lexer {
let lex: *Lexer = bux_alloc(sizeof(Lexer)) as *Lexer;
lex.source = source;
lex.sourceLen = bux_strlen(source) as int;
lex.pos = 0;
lex.line = 1;
lex.column = 1;
lex.startLine = 0;
lex.startColumn = 0;
lex.startPos = 0;
let tokBuf: *LexToken = bux_alloc(maxTokens as uint * sizeof(LexToken)) as *LexToken;
let diagBuf: *LexerDiag = bux_alloc(maxDiags as uint * sizeof(LexerDiag)) as *LexerDiag;
lex.tokens = tokBuf;
lex.diags = diagBuf;
lex.tokenCount = 0;
lex.diagCount = 0;
while true {
lexNextToken(lex);
if lex.tokenCount >= maxTokens {
lexEmitDiag(lex, "too many tokens");
break;
}
if lex.tokens[lex.tokenCount - 1].kind == tkEndOfFile {
break;
}
}
return lex;
}
func Lexer_TokenCount(lex: *Lexer) -> int {
return lex.tokenCount;
}
func Lexer_GetToken(lex: *Lexer, index: int) -> LexToken {
if index >= 0 && index < lex.tokenCount {
return lex.tokens[index];
}
var empty: LexToken = LexToken { kind: tkEndOfFile, text: "", line: 0, column: 0 };
return empty;
}
func Lexer_DiagCount(lex: *Lexer) -> int {
return lex.diagCount;
}
func Lexer_Free(lex: *Lexer) {
bux_free(lex.tokens as *void);
bux_free(lex.diags as *void);
bux_free(lex as *void);
}
}
+23
View File
@@ -0,0 +1,23 @@
// main.bux — Entry point for the Bux self-hosting compiler
module Main {
// C runtime for command-line args
extern func bux_argc() -> int;
extern func bux_argv(index: int) -> String;
extern func bux_alloc(size: uint) -> *void;
// Forward declaration from Cli module
func Cli_Run(args: *String, argCount: int) -> int;
func Main() -> int {
let count: int = bux_argc();
// Allocate array of String pointers
let args: *String = bux_alloc(count as uint * 8) as *String;
var i: int = 0;
while i < count {
args[i] = bux_argv(i);
i = i + 1;
}
return Cli_Run(args, count);
}
}
+89
View File
@@ -0,0 +1,89 @@
// manifest.bux — Manifest parser for bux.toml files
// Parses package metadata: name, version, type, build output.
module Manifest {
extern func bux_strlen(s: String) -> uint;
// ---------------------------------------------------------------------------
// Manifest struct
// ---------------------------------------------------------------------------
struct Manifest {
name: String;
version: String;
pkgType: String;
output: String;
}
// ---------------------------------------------------------------------------
// Simple TOML parser (handles [Package] and [Build] sections)
// ---------------------------------------------------------------------------
func Manifest_Parse(content: String) -> Manifest {
var m: Manifest;
m.name = "";
m.version = "0.1.0";
m.pkgType = "bin";
m.output = "Bin";
if String_Eq(content, "") { return m; }
var currentSection: String = "";
let count: uint = String_SplitCount(content, "\n");
var i: uint = 0;
while i < count {
let line: String = String_SplitPart(content, "\n", i);
// Skip empty lines and comments
if String_Eq(line, "") { i = i + 1; continue; }
if String_StartsWith(line, "#") { i = i + 1; continue; }
// Section header: [Section]
if String_StartsWith(line, "[") {
if String_StartsWith(line, "[Package]") {
currentSection = "Package";
} else if String_StartsWith(line, "[Build]") {
currentSection = "Build";
} else {
currentSection = "";
}
i = i + 1; continue;
}
// Key = Value
let eqCount: uint = String_SplitCount(line, "=");
if eqCount >= 2 {
let key: String = String_Trim(String_SplitPart(line, "=", 0));
let rawVal: String = String_Trim(String_SplitPart(line, "=", 1));
// Strip quotes from value
var val: String = rawVal;
if String_StartsWith(val, "\"") && String_EndsWith(val, "\"") {
let vlen: uint = bux_strlen(val);
if vlen >= 2 {
val = String_Slice(val, 1, vlen - 2);
}
}
if String_Eq(currentSection, "Package") {
if String_Eq(key, "Name") { m.name = val; }
if String_Eq(key, "Version") { m.version = val; }
if String_Eq(key, "Type") { m.pkgType = val; }
} else if String_Eq(currentSection, "Build") {
if String_Eq(key, "Output") { m.output = val; }
}
}
i = i + 1;
}
return m;
}
// ---------------------------------------------------------------------------
// Load manifest from file
// ---------------------------------------------------------------------------
func Manifest_Load(path: String) -> Manifest {
let content: String = ReadFile(path);
return Manifest_Parse(content);
}
}
+812
View File
@@ -0,0 +1,812 @@
// nim_backend.bux — Nim transpiler backend
// Generates Nim code from the HIR. Much simpler than C backend.
import Std::String;
import Std::Io::{Print, PrintLine};
module NimBackend {
// ---------------------------------------------------------------------------
// Type → Nim type name
// ---------------------------------------------------------------------------
func NimBackend_TypeToNim(kind: int) -> String {
if kind == tyVoid { return "void"; }
if kind == tyBool || kind == tyBool8 || kind == tyBool16 || kind == tyBool32 { return "bool"; }
if kind == tyChar8 { return "char"; }
if kind == tyChar16 { return "uint16"; }
if kind == tyChar32 { return "uint32"; }
if kind == tyStr { return "string"; }
if kind == tyInt8 { return "int8"; }
if kind == tyInt16 { return "int16"; }
if kind == tyInt32 { return "int32"; }
if kind == tyInt64 { return "int64"; }
if kind == tyInt { return "int"; }
if kind == tyUInt8 { return "uint8"; }
if kind == tyUInt16 { return "uint16"; }
if kind == tyUInt32 { return "uint32"; }
if kind == tyUInt64 { return "uint64"; }
if kind == tyUInt { return "uint"; }
if kind == tyFloat32 { return "float32"; }
if kind == tyFloat64 { return "float64"; }
if kind == tyPointer { return "pointer"; }
return "int";
}
// ---------------------------------------------------------------------------
// Operator → Nim
// ---------------------------------------------------------------------------
func NimBackend_OpToNim(op: int) -> String {
if op == tkPlus { return "+"; }
if op == tkMinus { return "-"; }
if op == tkStar { return "*"; }
if op == tkSlash { return "/"; }
if op == tkPercent { return "%"; }
if op == tkEq { return "=="; }
if op == tkNe { return "!="; }
if op == tkLt { return "<"; }
if op == tkLe { return "<="; }
if op == tkGt { return ">"; }
if op == tkGe { return ">="; }
if op == tkAmpAmp { return "and"; }
if op == tkPipePipe { return "or"; }
if op == tkBang { return "not"; }
if op == tkAmp { return "and"; }
if op == tkPipe { return "or"; }
if op == tkCaret { return "xor"; }
if op == tkShl { return "shl"; }
if op == tkShr { return "shr"; }
if op == tkAssign { return "="; }
return "?";
}
// ---------------------------------------------------------------------------
// Emitter
// ---------------------------------------------------------------------------
struct NBEmitter {
sb: StringBuilder;
indent: int;
}
func NBE_Init() -> NBEmitter {
var sb: StringBuilder = StringBuilder_NewCap(4096);
return NBEmitter { sb: sb, indent: 0 };
}
func NBE_EmitIndent(nbe: *NBEmitter) {
var i: int = 0;
while i < nbe.indent {
StringBuilder_Append(&nbe.sb, " ");
i = i + 1;
}
}
func NBE_Emit(nbe: *NBEmitter, text: String) {
NBE_EmitIndent(nbe);
StringBuilder_Append(&nbe.sb, text);
}
func NBE_EmitLine(nbe: *NBEmitter, text: String) {
NBE_Emit(nbe, text);
StringBuilder_Append(&nbe.sb, "\n");
}
func NBE_Build(nbe: *NBEmitter) -> String {
return StringBuilder_Build(&nbe.sb);
}
func NBE_Append(nbe: *NBEmitter, text: String) {
StringBuilder_Append(&nbe.sb, text);
}
func NBE_AppendLine(nbe: *NBEmitter, text: String) {
StringBuilder_Append(&nbe.sb, text);
StringBuilder_Append(&nbe.sb, "\n");
}
func NBE_AppendChar(nbe: *NBEmitter, c: char8) {
var tmp: *char8 = bux_alloc(2) as *char8;
tmp[0] = c;
tmp[1] = 0 as char8;
StringBuilder_Append(&nbe.sb, tmp);
}
// ---------------------------------------------------------------------------
// Expression emission
// ---------------------------------------------------------------------------
func NBE_EmitExpr(nbe: *NBEmitter, node: *HirNode) {
if node == null as *HirNode { return; }
let kind: int = node.kind;
// Literal
if kind == hLit {
if node.intValue == tkNull {
NBE_Append(nbe, "nil");
} else if node.intValue == tkStringLiteral {
// Nim string: emit quoted with escapes
NBE_Append(nbe, "\"");
var s: String = node.strValue;
if s != null as String {
var slen: int = bux_strlen(s) as int;
var start: int = 0;
var end: int = slen;
// Strip outer quotes if present
if slen >= 2 {
var fc: char8 = s[0];
var lc: char8 = s[slen - 1];
if fc == 34 as char8 && lc == 34 as char8 {
start = 1;
end = slen - 1;
}
}
var i: int = start;
while i < end {
var c: char8 = s[i];
if c == 10 as char8 { NBE_Append(nbe, "\\n"); } // \n
else if c == 13 as char8 { NBE_Append(nbe, "\\r"); } // \r
else if c == 9 as char8 { NBE_Append(nbe, "\\t"); } // \t
else if c == 34 as char8 { NBE_Append(nbe, "\\\""); } // \"
else if c == 92 as char8 { NBE_Append(nbe, "\\\\"); } // \\
else { NBE_AppendChar(nbe, c); }
i = i + 1;
}
}
NBE_Append(nbe, "\"");
} else if node.intValue == tkBoolLiteral {
NBE_Append(nbe, node.strValue);
} else {
NBE_Append(nbe, node.strValue);
}
return;
}
// Variable
if kind == hVar {
NBE_Append(nbe, NimBackend_EscapeIdent(node.strValue));
return;
}
// Binary
if kind == hBinary {
// Special case: String == null → check for empty string
if node.intValue == tkEq || node.intValue == tkNe {
var isNullCmp: bool = false;
var other: *HirNode = null as *HirNode;
if node.child1 != null as *HirNode && node.child1.kind == hLit && node.child1.intValue == tkNull {
isNullCmp = true;
other = node.child2;
}
if !isNullCmp && node.child2 != null as *HirNode && node.child2.kind == hLit && node.child2.intValue == tkNull {
isNullCmp = true;
other = node.child1;
}
if isNullCmp && other != null as *HirNode && other.typeKind == tyStr {
if node.intValue == tkEq {
NBE_EmitExpr(nbe, other);
NBE_Append(nbe, " == \"\"");
} else {
NBE_EmitExpr(nbe, other);
NBE_Append(nbe, " != \"\"");
}
return;
}
}
if node.child1 != null as *HirNode {
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, " ");
}
// Emit operator only if we have a left operand
if node.child1 != null as *HirNode {
NBE_Append(nbe, NimBackend_OpToNim(node.intValue));
NBE_Append(nbe, " ");
}
NBE_EmitExpr(nbe, node.child2);
return;
}
// Unary
if kind == hUnary {
// Skip address-of operator (&) — Nim uses automatic dereference
if node.intValue == tkAmp {
NBE_EmitExpr(nbe, node.child1);
return;
}
NBE_Append(nbe, NimBackend_OpToNim(node.intValue));
NBE_Append(nbe, "(");
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, ")");
return;
}
// Call
if kind == hCall {
var fname: String = node.strValue;
// Translate StringBuilder calls to Nim native operations
if String_Eq(fname, "StringBuilder_Append") {
// sb = sb & text (string concatenation)
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, " = ");
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, " & ");
NBE_EmitExpr(nbe, node.child2);
return;
}
if String_Eq(fname, "StringBuilder_NewCap") {
// Just empty string
NBE_Append(nbe, "\"\"");
return;
}
if String_Eq(fname, "StringBuilder_Build") {
// Just return the string field
NBE_EmitExpr(nbe, node.child1);
return;
}
NBE_Append(nbe, NimBackend_EscapeIdent(fname));
NBE_Append(nbe, "(");
if node.child1 != null as *HirNode {
NBE_EmitExpr(nbe, node.child1);
}
if node.child2 != null as *HirNode {
NBE_Append(nbe, ", ");
NBE_EmitExpr(nbe, node.child2);
}
NBE_Append(nbe, ")");
return;
}
// Field access: obj.field → obj.field (Nim auto-dereferences)
if kind == hFieldPtr || kind == hArrowField {
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, ".");
NBE_Append(nbe, node.strValue);
return;
}
// Index: arr[idx]
if kind == hIndexPtr {
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, "[");
NBE_EmitExpr(nbe, node.child2);
NBE_Append(nbe, "]");
return;
}
// Cast: expr as Type
if kind == hCast {
// Special case: null as Type → appropriate default
if node.child1 != null as *HirNode && node.child1.kind == hLit && node.child1.intValue == tkNull {
var typeName: String = node.typeName;
if !String_Eq(typeName, "") {
var converted: String = NimBackend_ConvertTypeName(typeName);
if String_Eq(converted, "string") {
NBE_Append(nbe, "\"\"");
return;
}
if String_Eq(converted, "pointer") {
NBE_Append(nbe, "nil");
return;
}
}
// For pointer types (ending in * or "ptr "), emit nil
var tlen: int = bux_strlen(typeName) as int;
if tlen > 1 {
var lastCh: char8 = typeName[tlen - 1];
if lastCh == 42 as char8 { // '*'
NBE_Append(nbe, "nil");
return;
}
}
}
NBE_Append(nbe, "cast[");
if !String_Eq(node.typeName, "") {
NBE_Append(nbe, NimBackend_ConvertTypeName(node.typeName));
} else {
NBE_Append(nbe, NimBackend_TypeToNim(node.typeKind));
}
NBE_Append(nbe, "](");
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, ")");
return;
}
// Is
if kind == hIs {
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, " is ");
if !String_Eq(node.typeName, "") {
NBE_Append(nbe, node.typeName);
}
return;
}
// SizeOf
if kind == hSizeOf {
NBE_Append(nbe, "sizeof(");
if !String_Eq(node.typeName, "") {
NBE_Append(nbe, node.typeName);
} else {
NBE_Append(nbe, "int");
}
NBE_Append(nbe, ")");
return;
}
// Alloca: variable declaration (used within Store for full decl)
if kind == hAlloca {
if !String_Eq(node.typeName, "") {
NBE_Append(nbe, NimBackend_ConvertTypeNameParam(node.typeName));
} else {
NBE_Append(nbe, NimBackend_TypeToNim(node.typeKind));
}
NBE_Append(nbe, " ");
NBE_Append(nbe, NimBackend_EscapeIdent(node.strValue));
return;
}
// Assign: target = value
if kind == hAssign {
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, " = ");
NBE_EmitExpr(nbe, node.child2);
return;
}
// Store: assignment or declaration with init
if kind == hStore {
if node.child1 != null as *HirNode && node.child1.kind == hAlloca {
// Declaration: var x: Type = value
NBE_Append(nbe, "var ");
NBE_Append(nbe, NimBackend_EscapeIdent(node.child1.strValue));
if !String_Eq(node.child1.typeName, "") {
NBE_Append(nbe, ": ");
NBE_Append(nbe, NimBackend_ConvertTypeNameParam(node.child1.typeName));
}
if node.child2 != null as *HirNode {
NBE_Append(nbe, " = ");
NBE_EmitExpr(nbe, node.child2);
}
return;
}
// Plain assignment
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, " = ");
NBE_EmitExpr(nbe, node.child2);
return;
}
// If
if kind == hIf {
NBE_Append(nbe, "if ");
NBE_EmitExpr(nbe, node.child1);
NBE_AppendLine(nbe, ":");
if node.child2 != null as *HirNode {
nbe.indent = nbe.indent + 1;
if node.child2.kind != hBlock { NBE_EmitIndent(nbe); }
NBE_EmitExpr(nbe, node.child2);
nbe.indent = nbe.indent - 1;
} else {
nbe.indent = nbe.indent + 1;
NBE_EmitLine(nbe, "discard");
nbe.indent = nbe.indent - 1;
}
if node.child3 != null as *HirNode {
NBE_EmitIndent(nbe); // indent else at same level as if
NBE_AppendLine(nbe, "else:");
nbe.indent = nbe.indent + 1;
if node.child3.kind != hBlock { NBE_EmitIndent(nbe); }
NBE_EmitExpr(nbe, node.child3);
nbe.indent = nbe.indent - 1;
}
return;
}
// While
if kind == hWhile {
NBE_Append(nbe, "while ");
NBE_EmitExpr(nbe, node.child1);
NBE_AppendLine(nbe, ":");
if node.child2 != null as *HirNode {
nbe.indent = nbe.indent + 1;
if node.child2.kind != hBlock { NBE_EmitIndent(nbe); }
NBE_EmitExpr(nbe, node.child2);
nbe.indent = nbe.indent - 1;
}
return;
}
// Loop (infinite)
if kind == hLoop {
NBE_AppendLine(nbe, "while true:");
if node.child1 != null as *HirNode {
nbe.indent = nbe.indent + 1;
if node.child1.kind != hBlock { NBE_EmitIndent(nbe); }
NBE_EmitExpr(nbe, node.child1);
nbe.indent = nbe.indent - 1;
}
return;
}
// Break / Continue
if kind == hBreak {
NBE_AppendLine(nbe, "break");
return;
}
if kind == hContinue {
NBE_AppendLine(nbe, "continue");
return;
}
// Return
if kind == hReturn {
NBE_Append(nbe, "return ");
if node.child1 != null as *HirNode {
if node.child1.kind == hBinary {
NBE_Append(nbe, "(");
NBE_EmitExpr(nbe, node.child1);
NBE_Append(nbe, ")");
} else {
NBE_EmitExpr(nbe, node.child1);
}
}
return;
}
// Struct init: TypeName { field: value, ... }
if kind == hStructInit {
NBE_Append(nbe, node.strValue);
NBE_Append(nbe, "(");
// Emit fields (chained via child3)
var field: *HirNode = node.child3;
var first: bool = true;
while field != null as *HirNode {
if !first { NBE_Append(nbe, ", "); }
NBE_Append(nbe, field.strValue);
NBE_Append(nbe, ": ");
NBE_EmitExpr(nbe, field.child1);
first = false;
field = field.child3;
}
NBE_Append(nbe, ")");
return;
}
// Load: dereference — in Nim just emit the pointer expression
if kind == hLoad {
NBE_EmitExpr(nbe, node.child1);
return;
}
// Block — sequence of statements
if kind == hBlock {
var child: *HirNode = node.child1;
if child == null as *HirNode {
NBE_EmitIndent(nbe);
NBE_AppendLine(nbe, "discard");
return;
}
// First pass: emit statements, merging continuation lines
while child != null as *HirNode {
NBE_EmitIndent(nbe);
// Check if this is a return/binary that has continuation siblings
if child.kind == hReturn && child.child1 != null as *HirNode {
// Emit return and open paren before expression
NBE_Append(nbe, "return (");
NBE_EmitExpr(nbe, child.child1);
child = child.child3;
// Merge all continuation siblings
while child != null as *HirNode && child.kind == hBinary && child.child1 == null as *HirNode {
NBE_Append(nbe, " ");
NBE_EmitExpr(nbe, child);
child = child.child3;
}
NBE_AppendLine(nbe, ")");
} else {
// Wrap with discard for non-control-flow statements
if child.kind == hCall || child.kind == hAssign || child.kind == hStore {
NBE_Append(nbe, "discard (");
NBE_EmitExpr(nbe, child);
NBE_Append(nbe, ")");
} else {
NBE_EmitExpr(nbe, child);
}
child = child.child3;
// Merge continuation siblings for other statement types
while child != null as *HirNode && child.kind == hBinary && child.child1 == null as *HirNode {
NBE_Append(nbe, " ");
NBE_EmitExpr(nbe, child);
child = child.child3;
}
NBE_AppendLine(nbe, "");
}
}
return;
}
// Match — simplified
if kind == hMatch {
NBE_EmitLine(nbe, "# match (simplified)");
return;
}
// Spawn
if kind == hSpawn {
NBE_Append(nbe, "spawn ");
NBE_Append(nbe, node.strValue);
NBE_Append(nbe, "(");
if node.child1 != null as *HirNode {
NBE_EmitExpr(nbe, node.child1);
}
NBE_Append(nbe, ")");
return;
}
// Await
if kind == hAwait {
NBE_Append(nbe, "await ");
NBE_EmitExpr(nbe, node.child1);
return;
}
// Fallback
NBE_Append(nbe, "nil");
}
// ---------------------------------------------------------------------------
// Function declaration emission
// ---------------------------------------------------------------------------
func NBE_EmitFuncDecl(nbe: *NBEmitter, f: *HirFunc) {
// Return type
var retType: String = f.retTypeName;
if String_Eq(retType, "") || String_Eq(retType, "void") {
// No return type annotation needed
} else if String_Eq(retType, "int") {
NBE_Append(nbe, "int");
} else if String_Eq(retType, "bool") {
NBE_Append(nbe, "bool");
} else if String_Eq(retType, "string") || String_Eq(retType, "String") {
NBE_Append(nbe, "string");
} else {
NBE_Append(nbe, retType);
}
}
// ---------------------------------------------------------------------------
// Convert type name from Bux to Nim
// ---------------------------------------------------------------------------
func NimBackend_EscapeIdent(name: String) -> String {
if name == null as String || String_Eq(name, "") { return "x"; }
// Nim keywords that need escaping
if String_Eq(name, "block") { return "`block`"; }
if String_Eq(name, "type") { return "`type`"; }
if String_Eq(name, "object") { return "`object`"; }
if String_Eq(name, "proc") { return "`proc`"; }
if String_Eq(name, "var") { return "`var`"; }
if String_Eq(name, "let") { return "`let`"; }
if String_Eq(name, "const") { return "`const`"; }
if String_Eq(name, "from") { return "`from`"; }
if String_Eq(name, "import") { return "`import`"; }
if String_Eq(name, "export") { return "`export`"; }
if String_Eq(name, "include") { return "`include`"; }
if String_Eq(name, "iterator") { return "`iterator`"; }
if String_Eq(name, "method") { return "`method`"; }
if String_Eq(name, "func") { return "`func`"; }
if String_Eq(name, "string") { return "`string`"; }
if String_Eq(name, "int") { return "`int`"; }
if String_Eq(name, "bool") { return "`bool`"; }
if String_Eq(name, "float") { return "`float`"; }
if String_Eq(name, "char") { return "`char`"; }
if String_Eq(name, "ptr") { return "`ptr`"; }
if String_Eq(name, "ref") { return "`ref`"; }
if String_Eq(name, "nil") { return "`nil`"; }
if String_Eq(name, "true") { return "`true`"; }
if String_Eq(name, "false") { return "`false`"; }
if String_Eq(name, "and") { return "`and`"; }
if String_Eq(name, "or") { return "`or`"; }
if String_Eq(name, "not") { return "`not`"; }
if String_Eq(name, "xor") { return "`xor`"; }
if String_Eq(name, "shl") { return "`shl`"; }
if String_Eq(name, "shr") { return "`shr`"; }
if String_Eq(name, "div") { return "`div`"; }
if String_Eq(name, "mod") { return "`mod`"; }
if String_Eq(name, "is") { return "`is`"; }
if String_Eq(name, "of") { return "`of`"; }
if String_Eq(name, "in") { return "`in`"; }
if String_Eq(name, "out") { return "`out`"; }
if String_Eq(name, "static") { return "`static`"; }
if String_Eq(name, "enum") { return "`enum`"; }
if String_Eq(name, "tuple") { return "`tuple`"; }
if String_Eq(name, "concept") { return "`concept`"; }
if String_Eq(name, "distinct") { return "`distinct`"; }
if String_Eq(name, "Result") { return "`Result`"; }
return name;
}
func NimBackend_ConvertTypeNameParam(tn: String) -> String {
// For function parameters: strip pointer indirection, Nim passes by ref
if tn == null as String || String_Eq(tn, "") { return "int"; }
if String_Eq(tn, "String") { return "string"; }
if String_Eq(tn, "bool") { return "bool"; }
if String_Eq(tn, "int") { return "int"; }
if String_Eq(tn, "int64") { return "int64"; }
if String_Eq(tn, "uint32") { return "uint32"; }
if String_Eq(tn, "uint") { return "uint"; }
if String_Eq(tn, "float64") { return "float64"; }
// Pointer types → ptr types in Nim
if !String_Eq(tn, "") {
var tlen: int = bux_strlen(tn) as int;
if tlen > 1 {
var lastCh: char8 = tn[tlen - 1];
if lastCh == 42 as char8 {
var base: String = String_Slice(tn, 0 as uint, (tlen - 1) as uint);
if String_Eq(base, "void") { return "pointer"; }
return String_Concat("ptr ", base);
}
}
}
return tn;
}
func NimBackend_ConvertTypeName(tn: String) -> String {
if tn == null as String || String_Eq(tn, "") { return "int"; }
if String_Eq(tn, "String") { return "string"; }
if String_Eq(tn, "bool") { return "bool"; }
if String_Eq(tn, "int") { return "int"; }
if String_Eq(tn, "int64") { return "int64"; }
if String_Eq(tn, "uint32") { return "uint32"; }
if String_Eq(tn, "uint") { return "uint"; }
if String_Eq(tn, "float64") { return "float64"; }
// Convert pointer types: "Foo*" → "ptr Foo"
if !String_Eq(tn, "") {
var tlen: int = bux_strlen(tn) as int;
if tlen > 1 {
var lastCh: char8 = tn[tlen - 1];
if lastCh == 42 as char8 { // '*'
// Extract base name (without *)
var base: String = String_Slice(tn, 0 as uint, (tlen - 1) as uint);
if String_Eq(base, "void") { return "pointer"; }
return String_Concat("ptr ", base);
}
}
}
return tn;
}
// ---------------------------------------------------------------------------
// Generate complete Nim module
// ---------------------------------------------------------------------------
func NimBackend_Generate(mod: *HirModule) -> String {
let nbe: *NBEmitter = bux_alloc(sizeof(NBEmitter)) as *NBEmitter;
nbe.sb = StringBuilder_NewCap(8192);
nbe.indent = 0;
// Header
NBE_AppendLine(nbe, "# Generated by Bux Nim Backend");
NBE_AppendLine(nbe, "");
NBE_AppendLine(nbe, "import std/strutils");
NBE_AppendLine(nbe, "import std/tables");
NBE_AppendLine(nbe, "import std/sequtils");
NBE_AppendLine(nbe, "import std/os");
NBE_AppendLine(nbe, "");
// Type definitions — skip builtins that Nim already has
NBE_AppendLine(nbe, "# Type aliases");
NBE_AppendLine(nbe, "type");
nbe.indent = nbe.indent + 1;
NBE_EmitLine(nbe, "String* = string");
NBE_EmitLine(nbe, "char8* = char");
// StringBuild is just a string in Nim
NBE_EmitLine(nbe, "StringBuilder* = string");
// Close type block
nbe.indent = nbe.indent - 1;
NBE_AppendLine(nbe, "");
NBE_AppendLine(nbe, "");
// Struct definitions
if mod.structCount > 0 {
NBE_AppendLine(nbe, "# Struct types");
NBE_AppendLine(nbe, "type");
var si: int = 0;
while si < mod.structCount {
NBE_Append(nbe, " ");
NBE_Append(nbe, NimBackend_EscapeIdent(mod.structs[si].name));
NBE_AppendLine(nbe, " = object");
var fi: int = 0;
while fi < mod.structs[si].fieldCount {
var fname: String = mod.structs[si].fields[fi].name;
if !String_Eq(fname, "") {
NBE_Append(nbe, " ");
NBE_Append(nbe, NimBackend_EscapeIdent(fname));
NBE_Append(nbe, ": ");
var ft: String = mod.structs[si].fields[fi].typeName;
NBE_Append(nbe, NimBackend_ConvertTypeName(ft));
NBE_AppendLine(nbe, "");
}
fi = fi + 1;
}
si = si + 1;
}
NBE_AppendLine(nbe, "");
}
// Const definitions
if mod.constCount > 0 {
NBE_AppendLine(nbe, "# Constants");
var ci: int = 0;
while ci < mod.constCount {
NBE_Emit(nbe, "const ");
NBE_Append(nbe, mod.consts[ci].name);
NBE_Append(nbe, " = ");
NBE_Append(nbe, String_FromInt(mod.consts[ci].value as int64));
NBE_AppendLine(nbe, "");
ci = ci + 1;
}
NBE_AppendLine(nbe, "");
}
// Extern declarations — skip (Nim doesn't need them)
// Bux extern functions are C runtime functions that Nim can't directly call.
// Instead, we use Nim native equivalents (e.g., len() instead of bux_strlen).
// Function definitions (reverse order: dependencies first)
var i: int = mod.funcCount;
while i > 0 {
i = i - 1;
let f: *HirFunc = &mod.funcs[i];
if f.body == null as *HirNode { continue; }
NBE_Emit(nbe, "proc ");
NBE_Append(nbe, NimBackend_EscapeIdent(f.name));
NBE_Append(nbe, "(");
// Parameters
var pi: int = 0;
var pnames: *HirParam = &f.param0;
while pi < f.paramCount {
if pi > 0 { NBE_Append(nbe, ", "); }
NBE_Append(nbe, NimBackend_EscapeIdent(pnames[pi].name));
NBE_Append(nbe, ": ");
var pt: String = pnames[pi].typeName;
NBE_Append(nbe, NimBackend_ConvertTypeNameParam(pt));
pi = pi + 1;
}
NBE_Append(nbe, "): ");
// Return type
var rtn: String = NimBackend_ConvertTypeNameParam(f.retTypeName);
if String_Eq(rtn, "") || String_Eq(rtn, "void") {
NBE_AppendLine(nbe, "void =");
} else {
NBE_Append(nbe, rtn);
NBE_AppendLine(nbe, " =");
}
// Body
nbe.indent = nbe.indent + 1;
if f.body != null as *HirNode && f.body.kind != hBlock {
NBE_EmitIndent(nbe);
}
NBE_EmitExpr(nbe, f.body);
nbe.indent = nbe.indent - 1;
NBE_AppendLine(nbe, "");
NBE_AppendLine(nbe, "");
}
// Main entry point
NBE_AppendLine(nbe, "# Entry point");
NBE_AppendLine(nbe, "when isMainModule:");
NBE_EmitLine(nbe, " var args: seq[string] = @[]");
NBE_EmitLine(nbe, " var argCount = paramCount()");
NBE_EmitLine(nbe, " discard Main()");
NBE_EmitLine(nbe, "");
let result: String = NBE_Build(nbe);
return result;
}
} // module NimBackend
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+95
View File
@@ -0,0 +1,95 @@
// scope.bux — Symbol table with parent-chain lookup
module Scope {
// Symbol kinds
const skVar: int = 0;
const skFunc: int = 1;
const skType: int = 2;
const skConst: int = 3;
const skModule: int = 4;
// Maximum symbols per scope
const maxSymbols: int = 8192;
struct Symbol {
kind: int;
name: String;
typeKind: int;
typeName: String;
isMutable: bool;
isPublic: bool;
decl: *Decl; // associated declaration (for funcs, structs, enums)
}
struct Scope {
symbols: *Symbol;
count: int;
parent: *Scope;
}
// ---------------------------------------------------------------------------
// Scope operations
// ---------------------------------------------------------------------------
func Scope_New() -> Scope {
let sz: uint = maxSymbols as uint * sizeof(Symbol);
let data: *Symbol = bux_alloc(sz) as *Symbol;
return Scope { symbols: data, count: 0, parent: null as *Scope };
}
func Scope_NewChild(parent: *Scope) -> Scope {
let sz: uint = maxSymbols as uint * sizeof(Symbol);
let data: *Symbol = bux_alloc(sz) as *Symbol;
return Scope { symbols: data, count: 0, parent: parent };
}
func Scope_Define(scope: *Scope, sym: Symbol) -> bool {
// Check local scope for duplicates
var i: int = 0;
while i < scope.count {
if String_Eq(scope.symbols[i].name, sym.name) {
return false;
}
i = i + 1;
}
if scope.count < maxSymbols {
scope.symbols[scope.count] = sym;
scope.count = scope.count + 1;
return true;
}
return false;
}
func Scope_Lookup(scope: *Scope, name: String) -> Symbol {
var cur: *Scope = scope;
while cur != null as *Scope {
var i: int = 0;
while i < cur.count {
if String_Eq(cur.symbols[i].name, name) {
return cur.symbols[i];
}
i = i + 1;
}
cur = cur.parent;
}
var empty: Symbol = Symbol { kind: 0, name: "", typeKind: 0, typeName: "", isMutable: false, isPublic: false, decl: null as *Decl };
return empty;
}
func Scope_LookupLocal(scope: *Scope, name: String) -> Symbol {
var i: int = 0;
while i < scope.count {
if String_Eq(scope.symbols[i].name, name) {
return scope.symbols[i];
}
i = i + 1;
}
var empty: Symbol = Symbol { kind: 0, name: "", typeKind: 0, typeName: "", isMutable: false, isPublic: false, decl: null as *Decl };
return empty;
}
func Scope_Free(scope: *Scope) {
bux_free(scope.symbols as *void);
}
}
+753
View File
@@ -0,0 +1,753 @@
// sema.bux — Semantic analysis (type checker, ported from sema.nim)
// Validates types, resolves identifiers, checks function calls.
module Sema {
// ---------------------------------------------------------------------------
// Sema context
// ---------------------------------------------------------------------------
struct Sema {
module: *Module;
scope: *Scope;
typeTable: *void;
methodTable: *void;
diagCount: int;
diags: *SemaDiag;
hasError: bool;
currentRetType: int; // return type of the function being checked
}
struct SemaDiag {
line: uint32;
column: uint32;
message: String;
}
// ---------------------------------------------------------------------------
// Diagnostics
// ---------------------------------------------------------------------------
func Sema_EmitError(sema: *Sema, line: uint32, col: uint32, msg: String) {
if sema.diagCount < 256 {
sema.diags[sema.diagCount] = SemaDiag { line: line, column: col, message: msg };
sema.diagCount = sema.diagCount + 1;
}
sema.hasError = true;
}
// ---------------------------------------------------------------------------
// Symbol zero-init helper (bootstrap C backend does not zero-init structs)
// ---------------------------------------------------------------------------
func Sema_ZeroInitSymbol(sym: *Symbol) {
sym.kind = 0;
sym.name = "";
sym.typeKind = 0;
sym.typeName = "";
sym.isMutable = false;
sym.isPublic = false;
sym.decl = null as *Decl;
}
// ---------------------------------------------------------------------------
// Type resolution from TypeExpr → Type constants
// ---------------------------------------------------------------------------
func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int {
if te == null as *TypeExpr { return tyUnknown; }
let name: String = te.typeName;
if te.kind == tekPointer {
return tyPointer;
}
if String_Eq(name, "void") { return tyVoid; }
if String_Eq(name, "bool") { return tyBool; }
if String_Eq(name, "bool8") { return tyBool8; }
if String_Eq(name, "bool16") { return tyBool16; }
if String_Eq(name, "bool32") { return tyBool32; }
if String_Eq(name, "char8") { return tyChar8; }
if String_Eq(name, "char16") { return tyChar16; }
if String_Eq(name, "char32") { return tyChar32; }
if String_Eq(name, "String") { return tyStr; }
if String_Eq(name, "str") { return tyStr; }
if String_Eq(name, "int8") { return tyInt8; }
if String_Eq(name, "int16") { return tyInt16; }
if String_Eq(name, "int32") { return tyInt32; }
if String_Eq(name, "int64") { return tyInt64; }
if String_Eq(name, "int") { return tyInt; }
if String_Eq(name, "uint8") { return tyUInt8; }
if String_Eq(name, "uint16") { return tyUInt16; }
if String_Eq(name, "uint32") { return tyUInt32; }
if String_Eq(name, "uint64") { return tyUInt64; }
if String_Eq(name, "uint") { return tyUInt; }
if String_Eq(name, "float32") { return tyFloat32; }
if String_Eq(name, "float64") { return tyFloat64; }
if String_Eq(name, "float") { return tyFloat64; }
// Check type table for user-defined types (StringMap not yet supported)
// TODO: re-enable when StringMap generic is available
// if sema.typeTable != null as *void { ... }
return tyNamed; // assume named type
}
// ---------------------------------------------------------------------------
// Type predicates
// ---------------------------------------------------------------------------
func Sema_IsNumeric(kind: int) -> bool {
if kind == tyUnknown || kind == tyNamed || kind == tyTypeParam { return true; }
if kind == tyInt8 || kind == tyInt16 || kind == tyInt32 || kind == tyInt64 || kind == tyInt { return true; }
if kind == tyUInt8 || kind == tyUInt16 || kind == tyUInt32 || kind == tyUInt64 || kind == tyUInt { return true; }
if kind == tyFloat32 || kind == tyFloat64 { return true; }
return false;
}
func Sema_IsBool(kind: int) -> bool {
return kind == tyBool || kind == tyBool8 || kind == tyBool16 || kind == tyBool32;
}
func Sema_TypeName(kind: int) -> String {
if kind == tyUnknown { return "?"; }
if kind == tyVoid { return "void"; }
if kind == tyBool { return "bool"; }
if kind == tyInt{ return "int"; }
if kind == tyInt64 { return "int64"; }
if kind == tyUInt { return "uint"; }
if kind == tyFloat64 { return "float64"; }
if kind == tyStr { return "String"; }
if kind == tyPointer { return "*ptr"; }
if kind == tyNamed { return "user-type"; }
if kind == tyTypeParam { return "type-param"; }
return "?";
}
// ---------------------------------------------------------------------------
// Block checking helper
// ---------------------------------------------------------------------------
func Sema_CheckBlock(sema: *Sema, block: *Block) {
if block == null as *Block { return; }
var blockScope: Scope = Scope_NewChild(sema.scope);
let prevScope: *Scope = sema.scope;
sema.scope = &blockScope;
var stmt: *Stmt = block.firstStmt;
while stmt != null as *Stmt {
Sema_CheckStmt(sema, stmt);
stmt = stmt.nextStmt;
}
sema.scope = prevScope;
}
// ---------------------------------------------------------------------------
// Expression type checking
// ---------------------------------------------------------------------------
func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
if expr == null as *Expr { return tyUnknown; }
let kind: int = expr.kind;
// Debug: print expr kind
// Print("Sema_CheckExpr kind=");
// PrintInt(kind as int64);
// PrintLine("");
// Literal
if kind == ekLiteral {
let tk: int = expr.tokKind;
if tk == tkIntLiteral { return tyInt; }
if tk == tkFloatLiteral { return tyFloat64; }
if tk == tkStringLiteral { return tyStr; }
if tk == tkBoolLiteral { return tyBool; }
if tk == tkCharLiteral { return tyChar32; }
if tk == tkNull { return tyPointer; }
return tyUnknown;
}
// Identifier — look up in scope
if kind == ekIdent {
let sym: Symbol = Scope_Lookup(sema.scope, expr.strValue);
if sym.kind == 0 && !String_Eq(sym.name, expr.strValue) {
Sema_EmitError(sema, expr.line, expr.column, "undeclared identifier");
return tyUnknown;
}
if sym.typeName != null as String && !String_Eq(sym.typeName, "") {
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekNamed;
te.typeName = sym.typeName;
expr.refType = te;
}
return sym.typeKind;
}
// self
if kind == ekSelf {
let sym: Symbol = Scope_Lookup(sema.scope, "self");
if sym.kind == 0 && !String_Eq(sym.name, "self") {
Sema_EmitError(sema, expr.line, expr.column, "self outside method");
return tyUnknown;
}
return sym.typeKind;
}
// Binary
if kind == ekBinary {
let left: int = Sema_CheckExpr(sema, expr.child1);
let right: int = Sema_CheckExpr(sema, expr.child2);
let op: int = expr.intValue;
// Assignment operators return target type
if op == tkAssign || (op >= tkPlusAssign && op <= tkShrAssign) {
return left;
}
// Comparison operators return bool
if op >= tkEq && op <= tkGe { return tyBool; }
// Logical operators return bool
if op == tkAmpAmp || op == tkPipePipe || op == tkBang { return tyBool; }
// Arithmetic returns wider type
if !Sema_IsNumeric(left) || !Sema_IsNumeric(right) {
Sema_EmitError(sema, expr.line, expr.column, "arithmetic requires numeric operands");
}
if left == tyFloat64 || right == tyFloat64 { return tyFloat64; }
return tyInt;
}
// Unary
if kind == ekUnary {
let operand: int = Sema_CheckExpr(sema, expr.child1);
let op: int = expr.intValue;
if op == tkBang { return tyBool; }
if op == tkStar { return tyUnknown; } // dereference — resolve pointee
if op == tkAmp { return tyPointer; }
return operand;
}
// Assign
if kind == ekAssign {
let target: int = Sema_CheckExpr(sema, expr.child1);
let value: int = Sema_CheckExpr(sema, expr.child2);
// Basic type compatibility (permissive for now)
return target;
}
// Call
if kind == ekCall {
discard Sema_CheckExpr(sema, expr.child1);
var arg: *ExprList = expr.callArgs;
while arg != null as *ExprList {
discard Sema_CheckExpr(sema, arg.expr);
arg = arg.next;
}
// Try to resolve return type from function declaration
if expr.child1.kind == ekIdent {
let sym: Symbol = Scope_Lookup(sema.scope, expr.child1.strValue);
if sym.kind == skFunc && sym.decl != null as *Decl && sym.decl.retType != null as *TypeExpr {
return Sema_ResolveType(sema, sym.decl.retType);
}
}
return tyUnknown;
}
// Ternary
if kind == ekTernary {
return Sema_CheckExpr(sema, expr.child2); // then type
}
// Cast — return target type
if kind == ekCast {
if expr.refType != null as *TypeExpr {
return Sema_ResolveType(sema, expr.refType);
}
return tyUnknown;
}
// Try (?)
if kind == ekTry {
let inner: int = Sema_CheckExpr(sema, expr.child1);
return inner; // simplified
}
// Struct init: TypeName { field: value, ... }
if kind == ekStructInit {
return tyNamed;
}
// Field access
if kind == ekField {
discard Sema_CheckExpr(sema, expr.child1);
return tyUnknown;
}
// Index
if kind == ekIndex {
let obj: int = Sema_CheckExpr(sema, expr.child1);
let idx: int = Sema_CheckExpr(sema, expr.child2);
if !Sema_IsNumeric(idx) && idx != tyUnknown {
Sema_EmitError(sema, expr.line, expr.column, "index must be integer");
}
return tyUnknown;
}
// Block expression
if kind == ekBlock {
if expr.refBlock != null as *Block {
Sema_CheckBlock(sema, expr.refBlock);
}
return tyVoid;
}
return tyUnknown;
}
// ---------------------------------------------------------------------------
// Statement checking
// ---------------------------------------------------------------------------
func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
if stmt == null as *Stmt { return; }
let kind: int = stmt.kind;
// Let/var
if kind == skLet {
let initType: int = Sema_CheckExpr(sema, stmt.child1);
// Register variable in scope
var sym: Symbol;
sym.kind = skVar;
sym.name = stmt.strValue;
sym.typeKind = initType;
sym.typeName = "";
if stmt.refStmtType != null as *TypeExpr {
sym.typeName = stmt.refStmtType.typeName;
}
sym.isMutable = stmt.boolValue;
sym.isPublic = false;
sym.decl = null as *Decl;
discard Scope_Define(sema.scope, sym);
return;
}
// Return
if kind == skReturn {
if stmt.child1 != null as *Expr {
let retType: int = Sema_CheckExpr(sema, stmt.child1);
if sema.currentRetType != tyUnknown && retType != tyUnknown {
if retType != sema.currentRetType {
// Be permissive: allow numeric widening, pointer compatibility
if !Sema_IsNumeric(retType) || !Sema_IsNumeric(sema.currentRetType) {
// Allow pointer-type compatibility (String <-> *T, *T <-> *U)
let retIsPtr: bool = retType == tyPointer || retType == tyStr;
let expectedIsPtr: bool = sema.currentRetType == tyPointer || sema.currentRetType == tyStr;
if !retIsPtr || !expectedIsPtr {
Sema_EmitError(sema, stmt.line, stmt.column, "return type mismatch");
}
}
}
}
} else {
if sema.currentRetType != tyVoid && sema.currentRetType != tyUnknown {
Sema_EmitError(sema, stmt.line, stmt.column, "missing return value");
}
}
return;
}
// If
if kind == skIf {
let condType: int = Sema_CheckExpr(sema, stmt.child1);
if !Sema_IsBool(condType) && condType != tyUnknown {
Sema_EmitError(sema, stmt.line, stmt.column, "if condition must be bool");
}
Sema_CheckBlock(sema, stmt.refStmtBlock);
Sema_CheckBlock(sema, stmt.refStmtElse);
return;
}
// While
if kind == skWhile {
let condType: int = Sema_CheckExpr(sema, stmt.child1);
if !Sema_IsBool(condType) && condType != tyUnknown {
Sema_EmitError(sema, stmt.line, stmt.column, "while condition must be bool");
}
Sema_CheckBlock(sema, stmt.refStmtBlock);
return;
}
// Do-while
if kind == skDoWhile {
Sema_CheckBlock(sema, stmt.refStmtBlock);
let condType: int = Sema_CheckExpr(sema, stmt.child1);
if !Sema_IsBool(condType) && condType != tyUnknown {
Sema_EmitError(sema, stmt.line, stmt.column, "do-while condition must be bool");
}
return;
}
// Loop
if kind == skLoop {
Sema_CheckBlock(sema, stmt.refStmtBlock);
return;
}
// For
if kind == skFor {
discard Sema_CheckExpr(sema, stmt.child1);
var forScope: Scope = Scope_NewChild(sema.scope);
var loopSym: Symbol;
loopSym.kind = skVar;
loopSym.name = stmt.strValue;
loopSym.typeKind = tyUnknown;
loopSym.typeName = "";
loopSym.isMutable = true;
loopSym.isPublic = false;
loopSym.decl = null as *Decl;
discard Scope_Define(&forScope, loopSym);
let prevScope: *Scope = sema.scope;
sema.scope = &forScope;
Sema_CheckBlock(sema, stmt.refStmtBlock);
sema.scope = prevScope;
return;
}
// Match
if kind == skMatch {
discard Sema_CheckExpr(sema, stmt.child1);
return;
}
// Break / Continue
if kind == skBreak || kind == skContinue {
return;
}
// Expression statement
if kind == skExpr && stmt.child1 != null as *Expr {
discard Sema_CheckExpr(sema, stmt.child1);
return;
}
// Decl (nested)
if kind == skDecl {
if stmt.refStmtDecl != null as *Decl && stmt.refStmtDecl.kind == dkFunc {
Sema_EmitError(sema, stmt.line, stmt.column, "nested functions not yet supported");
}
return;
}
}
// ---------------------------------------------------------------------------
// Collect globals (register functions, structs, enums in scope)
// ---------------------------------------------------------------------------
func Sema_CollectGlobals(sema: *Sema) {
var decl: *Decl = sema.module.firstItem;
var funcCount: int = 0;
while decl != null as *Decl {
let dk: int = decl.kind;
// Function
if dk == dkFunc {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skFunc;
sym.name = decl.strValue;
sym.typeKind = tyFunc;
sym.isPublic = decl.isPublic;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
}
// Struct
if dk == dkStruct {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skType;
sym.name = decl.strValue;
sym.typeKind = tyNamed;
sym.isPublic = decl.isPublic;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
}
// Enum
if dk == dkEnum {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skType;
sym.name = decl.strValue;
sym.typeKind = tyNamed;
sym.isPublic = decl.isPublic;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
// Register enum variants as constants
var vi: int = 0;
while vi < decl.variantCount && vi < 8 {
var v: EnumVariant;
if vi == 0 { v = decl.variant0; }
else if vi == 1 { v = decl.variant1; }
else if vi == 2 { v = decl.variant2; }
else if vi == 3 { v = decl.variant3; }
else if vi == 4 { v = decl.variant4; }
else if vi == 5 { v = decl.variant5; }
else if vi == 6 { v = decl.variant6; }
else if vi == 7 { v = decl.variant7; }
let variantName: String = String_Concat(decl.strValue, "_");
let variantName2: String = String_Concat(variantName, v.name);
var vSym: Symbol;
vSym.kind = skConst;
vSym.name = variantName2;
vSym.typeKind = tyNamed;
vSym.typeName = String_Concat(decl.strValue, "_Tag");
vSym.isMutable = false;
vSym.isPublic = decl.isPublic;
vSym.decl = decl;
discard Scope_Define(sema.scope, vSym);
vi = vi + 1;
}
}
// Const
if dk == dkConst {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skConst;
sym.name = decl.strValue;
if decl.constType != null as *TypeExpr {
sym.typeKind = Sema_ResolveType(sema, decl.constType);
sym.typeName = decl.constType.typeName;
} else {
sym.typeKind = tyInt;
}
sym.isMutable = false;
sym.isPublic = decl.isPublic;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
}
// Use (import)
if dk == dkUse {
if decl.useKind == 2 {
// Multi-import: names are comma-separated in useNames
// For now, register the whole useNames string as a single func name
// (permissive fallback — real fix needs string split)
let namesStr: String = decl.useNames;
if !String_Eq(namesStr, "") {
// Simple split by comma using available string functions
var start: uint = 0;
var pos: uint = 0;
let totalLen: uint = String_Len(namesStr);
while pos <= totalLen {
let atEnd: bool = pos == totalLen;
let isComma: bool = false;
if pos < totalLen {
// Check if character at pos is comma
let chStr: String = bux_str_slice(namesStr, pos, 1);
isComma = String_Eq(chStr, ",");
}
if atEnd || isComma {
let nameLen: uint = pos - start;
if nameLen > 0 {
let name: String = bux_str_slice(namesStr, start, nameLen);
var sym: Symbol;
sym.kind = skFunc;
sym.name = name;
sym.typeKind = tyUnknown;
sym.typeName = "";
sym.isMutable = false;
sym.isPublic = true;
sym.decl = null as *Decl;
discard Scope_Define(sema.scope, sym);
}
start = pos + 1;
}
pos = pos + 1;
}
}
} else {
// Single import or glob — add last path segment
let path: String = decl.usePath;
if !String_Eq(path, "") {
// Find last :: segment
var lastSeg: String = path;
let containsColons: int = bux_str_contains(path, "::");
if containsColons != 0 {
// Try to get last segment by finding last ::
// Use a simple heuristic: slice from various positions
let pathLen: uint = String_Len(path);
var tryPos: uint = 0;
while tryPos < pathLen {
let slice: String = bux_str_slice(path, tryPos, pathLen - tryPos);
if String_StartsWith(slice, "::") {
lastSeg = bux_str_slice(slice, 2, String_Len(slice) - 2);
}
tryPos = tryPos + 1;
}
}
var sym: Symbol;
sym.kind = skFunc;
sym.name = lastSeg;
sym.typeKind = tyUnknown;
sym.typeName = "";
sym.isMutable = false;
sym.isPublic = true;
sym.decl = null as *Decl;
discard Scope_Define(sema.scope, sym);
}
}
}
// Const
if dk == dkConst {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skConst;
sym.name = decl.strValue;
sym.typeKind = tyUnknown;
sym.isPublic = decl.isPublic;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
}
// Extern function
if dk == dkExternFunc {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
sym.kind = skFunc;
sym.name = decl.strValue;
sym.typeKind = tyFunc;
sym.isPublic = true;
sym.decl = decl;
discard Scope_Define(sema.scope, sym);
}
decl = decl.childDecl2;
}
}
// ---------------------------------------------------------------------------
// Analyze — main entry point
// ---------------------------------------------------------------------------
func Sema_Analyze(mod: *Module) -> *Sema {
let s: *Sema = bux_alloc(sizeof(Sema)) as *Sema;
s.module = mod;
s.scope = bux_alloc(sizeof(Scope)) as *Scope;
s.scope.symbols = bux_alloc(1024 as uint * sizeof(Symbol)) as *Symbol;
s.scope.count = 0;
s.scope.parent = null as *Scope;
s.hasError = false;
s.diagCount = 0;
s.diags = bux_alloc(256 as uint * sizeof(SemaDiag)) as *SemaDiag;
s.typeTable = null as *void;
s.methodTable = null as *void;
s.currentRetType = tyVoid;
// First pass: collect globals
Sema_CollectGlobals(s);
// Second pass: check function bodies
var decl: *Decl = mod.firstItem;
while decl != null as *Decl {
if decl.kind == dkFunc && decl.refBody != null as *Block {
// Create function scope (child of global)
var funcScope: Scope = Scope_NewChild(s.scope);
// Add type params to scope
if decl.typeParamCount >= 1 {
var tpSym: Symbol;
Sema_ZeroInitSymbol(&tpSym);
tpSym.kind = skType;
tpSym.name = decl.typeParam0;
tpSym.typeKind = tyTypeParam;
tpSym.decl = null as *Decl;
discard Scope_Define(&funcScope, tpSym);
}
if decl.typeParamCount >= 2 {
var tpSym2: Symbol;
Sema_ZeroInitSymbol(&tpSym2);
tpSym2.kind = skType;
tpSym2.name = decl.typeParam1;
tpSym2.typeKind = tyTypeParam;
tpSym2.decl = null as *Decl;
discard Scope_Define(&funcScope, tpSym2);
}
// Add parameters to scope
var i: int = 0;
while i < decl.paramCount {
var p: *Param = null as *Param;
if i == 0 { p = &decl.param0; }
else if i == 1 { p = &decl.param1; }
else if i == 2 { p = &decl.param2; }
else if i == 3 { p = &decl.param3; }
else if i == 4 { p = &decl.param4; }
else if i == 5 { p = &decl.param5; }
else if i == 6 { p = &decl.param6; }
else if i == 7 { p = &decl.param7; }
else if i == 8 { p = &decl.param8; }
var pSym: Symbol;
Sema_ZeroInitSymbol(&pSym);
pSym.kind = skVar;
if p != null as *Param && p.refParamType != null as *TypeExpr {
pSym.typeKind = Sema_ResolveType(s, p.refParamType);
pSym.typeName = p.refParamType.typeName;
} else {
pSym.typeKind = tyInt;
pSym.typeName = "";
}
pSym.isMutable = false;
pSym.isPublic = false;
pSym.decl = null as *Decl;
if i == 0 { pSym.name = decl.param0.name; }
else if i == 1 { pSym.name = decl.param1.name; }
else if i == 2 { pSym.name = decl.param2.name; }
else if i == 3 { pSym.name = decl.param3.name; }
else if i == 4 { pSym.name = decl.param4.name; }
else if i == 5 { pSym.name = decl.param5.name; }
else if i == 6 { pSym.name = decl.param6.name; }
else if i == 7 { pSym.name = decl.param7.name; }
else if i == 8 { pSym.name = decl.param8.name; }
discard Scope_Define(&funcScope, pSym);
i = i + 1;
}
// Switch to function scope and check body statements
let prevScope: *Scope = s.scope;
s.scope = &funcScope;
// Set current function return type
if decl.retType != null as *TypeExpr {
s.currentRetType = Sema_ResolveType(s, decl.retType);
} else {
s.currentRetType = tyVoid;
}
// Check body statements
var stmt: *Stmt = decl.refBody.firstStmt;
while stmt != null as *Stmt {
Sema_CheckStmt(s, stmt);
stmt = stmt.nextStmt;
}
s.scope = prevScope;
}
decl = decl.childDecl2;
}
return s;
}
func Sema_HasError(sema: *Sema) -> bool {
return sema.hasError;
}
func Sema_DiagCount(sema: *Sema) -> int {
return sema.diagCount;
}
func Sema_Free(sema: *Sema) {
bux_free(sema.scope.symbols as *void);
bux_free(sema.scope as *void);
bux_free(sema.diags as *void);
bux_free(sema as *void);
}
}
+13
View File
@@ -0,0 +1,13 @@
// source_location.bux — Source position tracking
module SourceLocation {
struct SourceLocation {
line: uint32;
column: uint32;
offset: uint32;
}
func SourceLocation_New(line: uint32, column: uint32, offset: uint32) -> SourceLocation {
return SourceLocation { line: line, column: column, offset: offset };
}
}
+322
View File
@@ -0,0 +1,322 @@
// token.bux — Token kinds and helpers
module Token {
// ---------------------------------------------------------------------------
// TokenKind enum
// ---------------------------------------------------------------------------
// Literals
const tkIntLiteral: int = 0;
const tkFloatLiteral: int = 1;
const tkStringLiteral: int = 2;
const tkCharLiteral: int = 3;
const tkBoolLiteral: int = 4;
// Identifiers
const tkIdent: int = 5;
const tkUnderscore: int = 6;
// Control flow keywords
const tkIf: int = 7;
const tkElse: int = 8;
const tkWhile: int = 9;
const tkDo: int = 10;
const tkLoop: int = 11;
const tkFor: int = 12;
const tkIn: int = 13;
const tkBreak: int = 14;
const tkContinue: int = 15;
const tkReturn: int = 16;
const tkMatch: int = 17;
// Declaration keywords
const tkFunc: int = 18;
const tkLet: int = 19;
const tkVar: int = 20;
const tkConst: int = 21;
const tkType: int = 22;
const tkStruct: int = 23;
const tkEnum: int = 24;
const tkUnion: int = 25;
const tkInterface: int = 26;
const tkExtend: int = 27;
const tkModule: int = 28;
const tkImport: int = 29;
const tkPub: int = 30;
const tkExtern: int = 31;
// Other keywords
const tkAs: int = 32;
const tkIs: int = 33;
const tkNull: int = 34;
const tkSelf: int = 35;
const tkSuper: int = 36;
const tkSizeOf: int = 37;
// Punctuation
const tkLParen: int = 38;
const tkRParen: int = 39;
const tkLBrace: int = 40;
const tkRBrace: int = 41;
const tkLBracket: int = 42;
const tkRBracket: int = 43;
const tkComma: int = 44;
const tkSemicolon: int = 45;
const tkColon: int = 46;
const tkColonColon: int = 47;
const tkDot: int = 48;
const tkDotDot: int = 49;
const tkDotDotDot: int = 50;
const tkDotDotEqual: int = 51;
const tkArrow: int = 52;
const tkFatArrow: int = 53;
const tkAt: int = 54;
const tkHash: int = 55;
const tkQuestion: int = 56;
// Arithmetic operators
const tkPlus: int = 57;
const tkMinus: int = 58;
const tkStar: int = 59;
const tkSlash: int = 60;
const tkPercent: int = 61;
const tkStarStar: int = 62;
const tkPlusPlus: int = 63;
const tkMinusMinus: int = 64;
// Bitwise operators
const tkAmp: int = 65;
const tkPipe: int = 66;
const tkCaret: int = 67;
const tkTilde: int = 68;
const tkShl: int = 69;
const tkShr: int = 70;
// Logical operators
const tkAmpAmp: int = 71;
const tkPipePipe: int = 72;
const tkBang: int = 73;
// Comparison operators
const tkEq: int = 74;
const tkNe: int = 75;
const tkLt: int = 76;
const tkLe: int = 77;
const tkGt: int = 78;
const tkGe: int = 79;
// Assignment operators
const tkAssign: int = 80;
const tkPlusAssign: int = 81;
const tkMinusAssign: int = 82;
const tkStarAssign: int = 83;
const tkSlashAssign: int = 84;
const tkPercentAssign: int = 85;
const tkAmpAssign: int = 86;
const tkPipeAssign: int = 87;
const tkCaretAssign: int = 88;
const tkShlAssign: int = 89;
const tkShrAssign: int = 90;
// Compile-time intrinsics
const tkHashLine: int = 91;
const tkHashColumn: int = 92;
const tkHashFile: int = 93;
const tkHashFunction: int = 94;
const tkHashDate: int = 95;
const tkHashTime: int = 96;
const tkHashModule: int = 97;
// Special
const tkOwn: int = 98;
const tkNewLine: int = 99;
const tkEndOfFile: int = 100;
const tkUnknown: int = 101;
// Async / concurrency
const tkAsync: int = 102;
const tkAwait: int = 103;
const tkSpawn: int = 104;
const tkDiscard: int = 105;
// ---------------------------------------------------------------------------
// Token struct
// ---------------------------------------------------------------------------
struct Token {
kind: int;
text: String;
line: uint32;
column: uint32;
offset: uint32;
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
func Token_IsKeyword(kind: int) -> bool {
if kind >= tkIf && kind <= tkIn { return true; }
if kind >= tkBreak && kind <= tkMatch { return true; }
if kind >= tkFunc && kind <= tkExtern { return true; }
if kind >= tkAs && kind <= tkSuper { return true; }
if kind == tkSizeOf { return true; }
if kind >= tkAsync && kind <= tkSpawn { return true; }
return false;
}
func Token_IsLiteral(kind: int) -> bool {
if kind >= tkIntLiteral && kind <= tkBoolLiteral { return true; }
return false;
}
func Token_IsOperator(kind: int) -> bool {
if kind >= tkPlus && kind <= tkShrAssign { return true; }
return false;
}
func Token_IsEof(kind: int) -> bool {
return kind == tkEndOfFile;
}
func Token_KeywordKind(text: String) -> int {
if String_Eq(text, "func") { return tkFunc; }
if String_Eq(text, "let") { return tkLet; }
if String_Eq(text, "var") { return tkVar; }
if String_Eq(text, "const") { return tkConst; }
if String_Eq(text, "type") { return tkType; }
if String_Eq(text, "struct") { return tkStruct; }
if String_Eq(text, "enum") { return tkEnum; }
if String_Eq(text, "union") { return tkUnion; }
if String_Eq(text, "interface") { return tkInterface; }
if String_Eq(text, "extend") { return tkExtend; }
if String_Eq(text, "module") { return tkModule; }
if String_Eq(text, "import") { return tkImport; }
if String_Eq(text, "pub") { return tkPub; }
if String_Eq(text, "extern") { return tkExtern; }
if String_Eq(text, "if") { return tkIf; }
if String_Eq(text, "else") { return tkElse; }
if String_Eq(text, "while") { return tkWhile; }
if String_Eq(text, "do") { return tkDo; }
if String_Eq(text, "loop") { return tkLoop; }
if String_Eq(text, "for") { return tkFor; }
if String_Eq(text, "in") { return tkIn; }
if String_Eq(text, "break") { return tkBreak; }
if String_Eq(text, "continue") { return tkContinue; }
if String_Eq(text, "return") { return tkReturn; }
if String_Eq(text, "match") { return tkMatch; }
if String_Eq(text, "as") { return tkAs; }
if String_Eq(text, "is") { return tkIs; }
if String_Eq(text, "null") { return tkNull; }
if String_Eq(text, "self") { return tkSelf; }
if String_Eq(text, "super") { return tkSuper; }
if String_Eq(text, "sizeof") { return tkSizeOf; }
if String_Eq(text, "true") { return tkBoolLiteral; }
if String_Eq(text, "false") { return tkBoolLiteral; }
return tkIdent;
}
func Token_KindName(kind: int) -> String {
if kind == tkIntLiteral { return "integer literal"; }
if kind == tkFloatLiteral { return "float literal"; }
if kind == tkStringLiteral { return "string literal"; }
if kind == tkCharLiteral { return "char literal"; }
if kind == tkBoolLiteral { return "boolean literal"; }
if kind == tkIdent { return "identifier"; }
if kind == tkUnderscore { return "_"; }
if kind == tkSizeOf { return "sizeof"; }
if kind == tkIf { return "if"; }
if kind == tkElse { return "else"; }
if kind == tkWhile { return "while"; }
if kind == tkDo { return "do"; }
if kind == tkLoop { return "loop"; }
if kind == tkFor { return "for"; }
if kind == tkIn { return "in"; }
if kind == tkBreak { return "break"; }
if kind == tkContinue { return "continue"; }
if kind == tkReturn { return "return"; }
if kind == tkMatch { return "match"; }
if kind == tkFunc { return "func"; }
if kind == tkLet { return "let"; }
if kind == tkVar { return "var"; }
if kind == tkConst { return "const"; }
if kind == tkType { return "type"; }
if kind == tkStruct { return "struct"; }
if kind == tkEnum { return "enum"; }
if kind == tkUnion { return "union"; }
if kind == tkInterface { return "interface"; }
if kind == tkExtend { return "extend"; }
if kind == tkModule { return "module"; }
if kind == tkImport { return "import"; }
if kind == tkPub { return "pub"; }
if kind == tkExtern { return "extern"; }
if kind == tkAs { return "as"; }
if kind == tkIs { return "is"; }
if kind == tkNull { return "null"; }
if kind == tkSelf { return "self"; }
if kind == tkSuper { return "super"; }
if kind == tkLParen { return "("; }
if kind == tkRParen { return ")"; }
if kind == tkLBrace { return "{"; }
if kind == tkRBrace { return "}"; }
if kind == tkLBracket { return "["; }
if kind == tkRBracket { return "]"; }
if kind == tkComma { return ","; }
if kind == tkSemicolon { return ";"; }
if kind == tkColon { return ":"; }
if kind == tkColonColon { return "::"; }
if kind == tkDot { return "."; }
if kind == tkDotDot { return ".."; }
if kind == tkDotDotDot { return "..."; }
if kind == tkDotDotEqual { return "..="; }
if kind == tkArrow { return "->"; }
if kind == tkFatArrow { return "=>"; }
if kind == tkAt { return "@"; }
if kind == tkHash { return "#"; }
if kind == tkQuestion { return "?"; }
if kind == tkPlus { return "+"; }
if kind == tkMinus { return "-"; }
if kind == tkStar { return "*"; }
if kind == tkSlash { return "/"; }
if kind == tkPercent { return "%"; }
if kind == tkStarStar { return "**"; }
if kind == tkPlusPlus { return "++"; }
if kind == tkMinusMinus { return "--"; }
if kind == tkAmp { return "&"; }
if kind == tkPipe { return "|"; }
if kind == tkCaret { return "^"; }
if kind == tkTilde { return "~"; }
if kind == tkShl { return "<<"; }
if kind == tkShr { return ">>"; }
if kind == tkAmpAmp { return "&&"; }
if kind == tkPipePipe { return "||"; }
if kind == tkBang { return "!"; }
if kind == tkEq { return "=="; }
if kind == tkNe { return "!="; }
if kind == tkLt { return "<"; }
if kind == tkLe { return "<="; }
if kind == tkGt { return ">"; }
if kind == tkGe { return ">="; }
if kind == tkAssign { return "="; }
if kind == tkPlusAssign { return "+="; }
if kind == tkMinusAssign { return "-="; }
if kind == tkStarAssign { return "*="; }
if kind == tkSlashAssign { return "/="; }
if kind == tkPercentAssign { return "%="; }
if kind == tkAmpAssign { return "&="; }
if kind == tkPipeAssign { return "|="; }
if kind == tkCaretAssign { return "^="; }
if kind == tkShlAssign { return "<<="; }
if kind == tkShrAssign { return ">>="; }
if kind == tkHashLine { return "#line"; }
if kind == tkHashColumn { return "#column"; }
if kind == tkHashFile { return "#file"; }
if kind == tkHashFunction { return "#function"; }
if kind == tkHashDate { return "#date"; }
if kind == tkHashTime { return "#time"; }
if kind == tkHashModule { return "#module"; }
if kind == tkNewLine { return "newline"; }
if kind == tkEndOfFile { return "end of file"; }
return "unknown token";
}
}
+188
View File
@@ -0,0 +1,188 @@
// types.bux — Type system definitions and factories
module Types {
// ---------------------------------------------------------------------------
// TypeKind constants
// ---------------------------------------------------------------------------
const tyUnknown: int = 0;
const tyVoid: int = 1;
const tyBool: int = 2;
const tyBool8: int = 3;
const tyBool16: int = 4;
const tyBool32: int = 5;
const tyChar8: int = 6;
const tyChar16: int = 7;
const tyChar32: int = 8;
const tyStr: int = 9;
const tyInt8: int = 10;
const tyInt16: int = 11;
const tyInt32: int = 12;
const tyInt64: int = 13;
const tyInt: int = 14;
const tyUInt8: int = 15;
const tyUInt16: int = 16;
const tyUInt32: int = 17;
const tyUInt64: int = 18;
const tyUInt: int = 19;
const tyFloat32: int = 20;
const tyFloat64: int = 21;
const tyPointer: int = 22;
const tySlice: int = 23;
const tyRange: int = 24;
const tyTuple: int = 25;
const tyNamed: int = 26;
const tyTypeParam: int = 27;
const tyFunc: int = 28;
// ---------------------------------------------------------------------------
// Type struct
// ---------------------------------------------------------------------------
struct Type {
kind: int;
name: String;
// inner types stored as array of pointers (simplified)
innerKind1: int;
innerName1: String;
innerKind2: int;
innerName2: String;
innerKind3: int;
innerName3: String;
innerCount: int;
}
// ---------------------------------------------------------------------------
// Factories
// ---------------------------------------------------------------------------
func Type_MakeUnknown() -> Type {
return Type { kind: tyUnknown, name: "", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeVoid() -> Type {
return Type { kind: tyVoid, name: "void", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeBool() -> Type {
return Type { kind: tyBool, name: "bool", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeInt() -> Type {
return Type { kind: tyInt, name: "int", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeInt64() -> Type {
return Type { kind: tyInt64, name: "int64", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeUInt() -> Type {
return Type { kind: tyUInt, name: "uint", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeFloat64() -> Type {
return Type { kind: tyFloat64, name: "float64", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeStr() -> Type {
return Type { kind: tyStr, name: "String", innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakePointer(pointee: Type) -> Type {
return Type { kind: tyPointer, name: "", innerCount: 1,
innerKind1: pointee.kind, innerName1: pointee.name,
innerKind2: 0, innerName2: "", innerKind3: 0, innerName3: "" };
}
func Type_MakeNamed(name: String) -> Type {
return Type { kind: tyNamed, name: name, innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
func Type_MakeTypeParam(name: String) -> Type {
return Type { kind: tyTypeParam, name: name, innerCount: 0,
innerKind1: 0, innerName1: "", innerKind2: 0, innerName2: "",
innerKind3: 0, innerName3: "" };
}
// ---------------------------------------------------------------------------
// Predicates
// ---------------------------------------------------------------------------
func Type_IsNumeric(t: Type) -> bool {
let k: int = t.kind;
if k == tyInt8 || k == tyInt16 || k == tyInt32 || k == tyInt64 || k == tyInt { return true; }
if k == tyUInt8 || k == tyUInt16 || k == tyUInt32 || k == tyUInt64 || k == tyUInt { return true; }
if k == tyFloat32 || k == tyFloat64 { return true; }
if k == tyUnknown || k == tyNamed || k == tyTypeParam { return true; }
return false;
}
func Type_IsInteger(t: Type) -> bool {
let k: int = t.kind;
if k == tyInt8 || k == tyInt16 || k == tyInt32 || k == tyInt64 || k == tyInt { return true; }
if k == tyUInt8 || k == tyUInt16 || k == tyUInt32 || k == tyUInt64 || k == tyUInt { return true; }
if k == tyUnknown || k == tyNamed || k == tyTypeParam { return true; }
return false;
}
func Type_IsBool(t: Type) -> bool {
let k: int = t.kind;
return k == tyBool || k == tyBool8 || k == tyBool16 || k == tyBool32;
}
func Type_IsPointer(t: Type) -> bool {
return t.kind == tyPointer;
}
func Type_IsSlice(t: Type) -> bool {
return t.kind == tySlice;
}
// ---------------------------------------------------------------------------
// Comparison (structural, limited to kind + name for simplicity)
// ---------------------------------------------------------------------------
func Type_Eq(a: Type, b: Type) -> bool {
if a.kind != b.kind { return false; }
if a.kind == tyNamed || a.kind == tyTypeParam {
return String_Eq(a.name, b.name);
}
return true;
}
// ---------------------------------------------------------------------------
// toString
// ---------------------------------------------------------------------------
func Type_ToString(t: Type) -> String {
if t.kind == tyVoid { return "void"; }
if t.kind == tyBool { return "bool"; }
if t.kind == tyStr { return "String"; }
if t.kind == tyInt { return "int"; }
if t.kind == tyInt64 { return "int64"; }
if t.kind == tyUInt { return "uint"; }
if t.kind == tyFloat64 { return "float64"; }
if t.kind == tyNamed { return t.name; }
if t.kind == tyTypeParam { return t.name; }
if t.kind == tyPointer { return String_Concat("*", t.innerName1); }
return "?";
}
}
BIN
View File
Binary file not shown.
+117
View File
@@ -0,0 +1,117 @@
import std/[unittest, strutils]
import ../bootstrap/[lexer, parser, sema, types]
proc checkSource(source: string): SemaResult =
let lexRes = tokenize(source, "<test>")
check not lexRes.hasErrors
let parseRes = parse(lexRes.tokens, "<test>")
check parseRes.diagnostics.len == 0
result = analyze(parseRes.module)
suite "Borrow Checker":
test "@[Checked] function with &mut allows mutation":
let res = checkSource("""
func Mutate(val: &mut int) {
*val = 42;
}
func Main() -> int {
var x: int = 10;
Mutate(&x);
return 0;
}
""")
check(not res.hasErrors)
test "@[Checked] function rejects write through &T":
let res = checkSource("""
@[Checked]
func BadWrite(val: &int) {
*val = 42;
}
func Main() -> int {
var x: int = 10;
BadWrite(&x);
return 0;
}
""")
check(res.hasErrors)
check(res.diagnostics[0].message.contains("cannot assign through shared reference"))
test "unchecked function allows write through raw pointer":
let res = checkSource("""
func RawWrite(val: *int) {
*val = 42;
}
func Main() -> int {
var x: int = 10;
RawWrite(&x);
return 0;
}
""")
check(not res.hasErrors)
test "&T allows reading":
let res = checkSource("""
@[Checked]
func Read(val: &int) -> int {
return *val;
}
func Main() -> int {
var x: int = 10;
return Read(&x);
}
""")
check(not res.hasErrors)
test "own T type parses and resolves":
let res = checkSource("""
struct Box {
value: int;
}
func TakeOwn(b: own Box) -> int {
return b.value;
}
func Main() -> int {
var b: Box = Box { value: 42 };
return TakeOwn(b);
}
""")
check(not res.hasErrors)
test "@[Checked] rejects double mutable borrow in call":
let res = checkSource("""
@[Checked]
func Swap(a: &mut int, b: &mut int) {
let tmp = *a;
*a = *b;
*b = tmp;
}
@[Checked]
func Main() -> int {
var x: int = 10;
Swap(&x, &x);
return 0;
}
""")
check(res.hasErrors)
check(res.diagnostics[0].message.contains("mutable borrow"))
test "@[Checked] rejects use after move":
let res = checkSource("""
struct Box {
value: int;
}
@[Checked]
func Consume(b: own Box) -> int {
return b.value;
}
@[Checked]
func Main() -> int {
var b: Box = Box { value: 42 };
let x: int = Consume(b);
return b.value;
}
""")
check(res.hasErrors)
check(res.diagnostics[0].message.contains("moved"))
BIN
View File
Binary file not shown.
+104
View File
@@ -0,0 +1,104 @@
import std/[unittest, strformat]
import ../bootstrap/[lexer, parser, sema, hir, hir_lower, types, scope]
proc lowerSource(source: string): HirModule =
let lexRes = tokenize(source, "<test>")
check not lexRes.hasErrors
let parseRes = parse(lexRes.tokens, "<test>")
check parseRes.diagnostics.len == 0
# Create Sema and populate global scope + method table
var s = Sema(module: parseRes.module, globalScope: newScope())
s.collectGlobals()
result = lowerModule(parseRes.module, s)
suite "HIR Lowering":
test "simple function":
let src = "func Main() -> int { return 0; }"
let hir = lowerSource(src)
check hir.funcs.len == 1
check hir.funcs[0].name == "Main"
check hir.funcs[0].body != nil
check hir.funcs[0].body.kind == hBlock
echo " PASS: simple function"
test "function with arithmetic":
let src = "func Add(a: int, b: int) -> int { return a + b; }"
let hir = lowerSource(src)
check hir.funcs.len == 1
check hir.funcs[0].params.len == 2
echo " PASS: function with arithmetic"
test "struct lowering":
let src = """
struct Point { x: float64; y: float64; }
func Main() -> int { return 0; }
"""
let hir = lowerSource(src)
check hir.structs.len == 1
check hir.structs[0].name == "Point"
check hir.structs[0].fields.len == 2
echo " PASS: struct lowering"
test "method lowering":
let src = """
struct Point { x: float64; }
extend Point {
func GetX(self: Point) -> float64 { return 0.0; }
}
func Main() -> int { return 0; }
"""
let hir = lowerSource(src)
check hir.funcs.len == 2 # GetX (mangled) + Main
var foundMangled = false
for f in hir.funcs:
if f.name == "Point_GetX":
foundMangled = true
check foundMangled
echo " PASS: method lowering"
test "if statement":
let src = """
func Main() -> int {
if true { return 1; }
return 0;
}
"""
let hir = lowerSource(src)
let body = hir.funcs[0].body
check body.kind == hBlock
check body.blockStmts.len >= 1
echo " PASS: if statement"
test "while loop":
let src = """
func Main() -> int {
while true { break; }
return 0;
}
"""
let hir = lowerSource(src)
let body = hir.funcs[0].body
check body.kind == hBlock
echo " PASS: while loop"
test "let statement":
let src = """
func Main() -> int {
let x: int = 42;
return x;
}
"""
let hir = lowerSource(src)
check hir.funcs.len == 1
echo " PASS: let statement"
test "enum lowering":
let src = """
enum Color { Red, Green, Blue }
func Main() -> int { return 0; }
"""
let hir = lowerSource(src)
check hir.enums.len == 1
check hir.enums[0].name == "Color"
check hir.enums[0].variants.len == 3
echo " PASS: enum lowering"
BIN
View File
Binary file not shown.
+124
View File
@@ -0,0 +1,124 @@
import std/[unittest, os, strutils]
import ../bootstrap/[lexer, token, source_location]
proc tokenKinds(source: string): seq[TokenKind] =
let res = tokenize(source, "<test>")
for t in res.tokens:
result.add(t.kind)
proc tokenTexts(source: string): seq[string] =
let res = tokenize(source, "<test>")
for t in res.tokens:
result.add(t.text)
suite "Lexer":
test "empty source":
let res = tokenize("", "<test>")
check res.tokens.len == 1
check res.tokens[0].kind == tkEndOfFile
check not res.hasErrors
test "simple identifiers":
let kinds = tokenKinds("foo bar _x Baz123")
check kinds == @[tkIdent, tkIdent, tkIdent, tkIdent, tkEndOfFile]
test "keywords":
let kinds = tokenKinds("func let var if else while for return match struct enum")
check kinds == @[tkFunc, tkLet, tkVar, tkIf, tkElse, tkWhile, tkFor, tkReturn, tkMatch, tkStruct, tkEnum, tkEndOfFile]
test "bool literals":
let kinds = tokenKinds("true false")
check kinds == @[tkBoolLiteral, tkBoolLiteral, tkEndOfFile]
let texts = tokenTexts("true false")
check texts == @["true", "false", ""]
test "integers":
let kinds = tokenKinds("42 0xFF 0b1010 0o77")
check kinds == @[tkIntLiteral, tkIntLiteral, tkIntLiteral, tkIntLiteral, tkEndOfFile]
test "floats":
let kinds = tokenKinds("3.14 1.0e-9 2.5E+3")
check kinds == @[tkFloatLiteral, tkFloatLiteral, tkFloatLiteral, tkEndOfFile]
test "operators":
let kinds = tokenKinds("+ - * / % ** ++ --")
check kinds == @[tkPlus, tkMinus, tkStar, tkSlash, tkPercent, tkStarStar, tkPlusPlus, tkMinusMinus, tkEndOfFile]
test "comparison operators":
let kinds = tokenKinds("== != < <= > >=")
check kinds == @[tkEq, tkNe, tkLt, tkLe, tkGt, tkGe, tkEndOfFile]
test "assignment operators":
let kinds = tokenKinds("= += -= *= /= %= &= |= ^= <<= >>=")
check kinds == @[tkAssign, tkPlusAssign, tkMinusAssign, tkStarAssign, tkSlashAssign, tkPercentAssign, tkAmpAssign, tkPipeAssign, tkCaretAssign, tkShlAssign, tkShrAssign, tkEndOfFile]
test "punctuation":
let kinds = tokenKinds("( ) { } [ ] , ; : :: . .. ... ..= -> => @ # ?")
check kinds == @[tkLParen, tkRParen, tkLBrace, tkRBrace, tkLBracket, tkRBracket, tkComma, tkSemicolon, tkColon, tkColonColon, tkDot, tkDotDot, tkDotDotDot, tkDotDotEqual, tkArrow, tkFatArrow, tkAt, tkHash, tkQuestion, tkEndOfFile]
test "logical operators":
let kinds = tokenKinds("&& || !")
check kinds == @[tkAmpAmp, tkPipePipe, tkBang, tkEndOfFile]
test "bitwise operators":
let kinds = tokenKinds("& | ^ ~ << >>")
check kinds == @[tkAmp, tkPipe, tkCaret, tkTilde, tkShl, tkShr, tkEndOfFile]
test "string literals":
let kinds = tokenKinds("\"hello\" c8\"world\"")
check kinds == @[tkStringLiteral, tkStringLiteral, tkEndOfFile]
test "char literals":
let kinds = tokenKinds("'A' c8'B'")
check kinds == @[tkCharLiteral, tkCharLiteral, tkEndOfFile]
test "line comments are skipped":
let kinds = tokenKinds("let x = 42 // this is a comment")
check kinds == @[tkLet, tkIdent, tkAssign, tkIntLiteral, tkEndOfFile]
test "block comments are skipped":
let kinds = tokenKinds("let /* inline comment */ x = 42")
check kinds == @[tkLet, tkIdent, tkAssign, tkIntLiteral, tkEndOfFile]
test "nested block comments":
let kinds = tokenKinds("let /* outer /* inner */ still outer */ x = 42")
check kinds == @[tkLet, tkIdent, tkAssign, tkIntLiteral, tkEndOfFile]
test "intrinsics":
let kinds = tokenKinds("#line #column #file #function #date #time #module")
check kinds == @[tkHashLine, tkHashColumn, tkHashFile, tkHashFunction, tkHashDate, tkHashTime, tkHashModule, tkEndOfFile]
test "newline tokens":
let kinds = tokenKinds("let x = 42\nlet y = 10")
check kinds == @[tkLet, tkIdent, tkAssign, tkIntLiteral, tkNewLine, tkLet, tkIdent, tkAssign, tkIntLiteral, tkEndOfFile]
test "paths":
let kinds = tokenKinds("Std::Io::PrintLine")
check kinds == @[tkIdent, tkColonColon, tkIdent, tkColonColon, tkIdent, tkEndOfFile]
test "unterminated string error":
let res = tokenize("\"hello", "<test>")
check res.hasErrors
test "unterminated char error":
let res = tokenize("'a", "<test>")
check res.hasErrors
test "escape sequences in string":
let res = tokenize("\"hello\\nworld\\t!\"", "<test>")
check not res.hasErrors
check res.tokens[0].kind == tkStringLiteral
test "full function":
let src = "func Main() -> int {\n let x: int32 = 42;\n return x;\n}\n"
let res = tokenize(src, "<test>")
check not res.hasErrors
check res.tokens[0].kind == tkFunc
check res.tokens[1].kind == tkIdent
check res.tokens[1].text == "Main"
test "dumpTokens produces output":
let res = tokenize("let x = 42", "<test>")
let dump = dumpTokens(res)
check "let" in dump
check "42" in dump
BIN
View File
Binary file not shown.
+122
View File
@@ -0,0 +1,122 @@
import std/[unittest, os, strutils]
import ../bootstrap/[lexer, parser, ast, token]
proc parseSource(source: string): ParseResult =
let lexRes = tokenize(source, "<test>")
check not lexRes.hasErrors
result = parse(lexRes.tokens, "<test>")
suite "Parser":
test "empty module":
let res = parseSource("")
check res.diagnostics.len == 0
check res.module.items.len == 0
test "simple function":
let res = parseSource("func Main() -> int { return 0; }")
check res.diagnostics.len == 0
check res.module.items.len == 1
check res.module.items[0].kind == dkFunc
check res.module.items[0].declFuncName == "Main"
test "function with parameters":
let res = parseSource("func Add(a: int32, b: int32) -> int32 { return a + b; }")
check res.diagnostics.len == 0
check res.module.items[0].declFuncParams.len == 2
check res.module.items[0].declFuncParams[0].name == "a"
check res.module.items[0].declFuncParams[1].name == "b"
test "struct declaration":
let res = parseSource("struct Point { x: float64; y: float64; }")
check res.diagnostics.len == 0
check res.module.items[0].kind == dkStruct
check res.module.items[0].declStructName == "Point"
check res.module.items[0].declStructFields.len == 2
test "enum declaration":
let res = parseSource("enum Color { Red, Green, Blue }")
check res.diagnostics.len == 0
check res.module.items[0].kind == dkEnum
check res.module.items[0].declEnumName == "Color"
check res.module.items[0].declEnumVariants.len == 3
test "import declaration":
let res = parseSource("import Std::Io::PrintLine;")
check res.diagnostics.len == 0
check res.module.items[0].kind == dkUse
check res.module.items[0].declUsePath == @["Std", "Io", "PrintLine"]
test "const declaration":
let res = parseSource("const PI: float64 = 3.14;")
check res.diagnostics.len == 0
check res.module.items[0].kind == dkConst
check res.module.items[0].declConstName == "PI"
test "type alias":
let res = parseSource("type MyInt = int32;")
check res.diagnostics.len == 0
check res.module.items[0].kind == dkTypeAlias
check res.module.items[0].declAliasName == "MyInt"
test "let statement in function":
let res = parseSource("func Main() -> int { let x: int32 = 42; return x; }")
check res.diagnostics.len == 0
check res.module.items[0].declFuncBody.stmts.len == 2
check res.module.items[0].declFuncBody.stmts[0].kind == skLet
check res.module.items[0].declFuncBody.stmts[0].stmtLetName == "x"
test "if statement":
let res = parseSource("func Main() -> int { if true { return 1; } else { return 0; } }")
check res.diagnostics.len == 0
let body = res.module.items[0].declFuncBody
check body.stmts[0].kind == skIf
check body.stmts[0].stmtIfThen.stmts.len == 1
check body.stmts[0].stmtIfElse.stmts.len == 1
test "while loop":
let res = parseSource("func Main() -> int { while true { break; } return 0; }")
check res.diagnostics.len == 0
let body = res.module.items[0].declFuncBody
check body.stmts[0].kind == skWhile
check body.stmts[0].stmtWhileBody.stmts[0].kind == skBreak
test "for loop":
let res = parseSource("func Main() -> int { for i in items { continue; } return 0; }")
check res.diagnostics.len == 0
let body = res.module.items[0].declFuncBody
check body.stmts[0].kind == skFor
check body.stmts[0].stmtForVar == "i"
test "match expression":
let res = parseSource("func Main() -> int { let x = match 1 { 1 => 10, _ => 20 }; return x; }")
check res.diagnostics.len == 0
let body = res.module.items[0].declFuncBody
check body.stmts[0].kind == skLet
check body.stmts[0].stmtLetInit.kind == ekMatch
test "binary expression":
let res = parseSource("func Main() -> int { return 1 + 2 * 3; }")
check res.diagnostics.len == 0
let ret = res.module.items[0].declFuncBody.stmts[0]
check ret.kind == skReturn
check ret.stmtReturnValue.kind == ekBinary
# 1 + (2 * 3)
check ret.stmtReturnValue.exprBinaryOp == tkPlus
test "call expression":
let res = parseSource("func Main() -> int { return Add(10, 20); }")
check res.diagnostics.len == 0
let ret = res.module.items[0].declFuncBody.stmts[0]
check ret.stmtReturnValue.kind == ekCall
check ret.stmtReturnValue.exprCallCallee.kind == ekIdent
test "full sample file":
let source = readFile(currentSourcePath.parentDir / "testdata" / "sample.bux")
let lexRes = tokenize(source, "sample.bux")
check not lexRes.hasErrors
let parseRes = parse(lexRes.tokens, "sample.bux")
check parseRes.diagnostics.len == 0
check parseRes.module.items.len == 3
check parseRes.module.items[0].kind == dkUse
check parseRes.module.items[1].kind == dkFunc
check parseRes.module.items[2].kind == dkFunc
BIN
View File
Binary file not shown.
+139
View File
@@ -0,0 +1,139 @@
import std/[unittest, strutils]
import ../bootstrap/[lexer, parser, sema, types]
proc checkSource(source: string): SemaResult =
let lexRes = tokenize(source, "<test>")
check not lexRes.hasErrors
let parseRes = parse(lexRes.tokens, "<test>")
check parseRes.diagnostics.len == 0
result = analyze(parseRes.module)
suite "Sema":
test "valid function with correct types":
let res = checkSource("func Main() -> int { return 0; }")
check not res.hasErrors
test "undeclared identifier":
let res = checkSource("func Main() -> int { return x; }")
check res.hasErrors
check "undeclared" in res.diagnostics[0].message
test "duplicate function":
let res = checkSource("func Main() -> int { return 0; } func Main() -> int { return 1; }")
check res.hasErrors
check "duplicate" in res.diagnostics[0].message
test "type mismatch in assignment":
let res = checkSource("func Main() -> int { let x: int32 = c8\"hello\"; return 0; }")
check res.hasErrors
check "cannot assign" in res.diagnostics[0].message
test "valid arithmetic":
let res = checkSource("func Main() -> int { return 1 + 2 * 3; }")
check not res.hasErrors
test "arithmetic on strings fails":
let res = checkSource("func Main() -> int { return c8\"a\" + c8\"b\"; }")
check res.hasErrors
test "valid function call":
let res = checkSource("func Add(a: int, b: int) -> int { return a + b; } func Main() -> int { return Add(1, 2); }")
check not res.hasErrors
test "wrong number of arguments":
let res = checkSource("func Add(a: int32, b: int32) -> int32 { return a + b; } func Main() -> int { return Add(1); }")
check res.hasErrors
check "expected 2 arguments" in res.diagnostics[0].message
test "wrong argument type":
let res = checkSource("func Add(a: int32, b: int32) -> int32 { return a + b; } func Main() -> int { return Add(c8\"a\", 2); }")
check res.hasErrors
check "argument 1" in res.diagnostics[0].message
test "if condition must be bool":
let res = checkSource("func Main() -> int { if 1 { return 0; } return 0; }")
check res.hasErrors
check "bool" in res.diagnostics[0].message
test "valid if with bool":
let res = checkSource("func Main() -> int { if true { return 0; } return 0; }")
check not res.hasErrors
test "pointer dereference":
let res = checkSource("func Main() -> int32 { let p: *int32 = null; return *p; }")
check not res.hasErrors
test "struct field access":
let res = checkSource("struct Point { x: float64; y: float64; } func Main() -> int32 { let p = Point { x: 1.0, y: 2.0 }; return 0; }")
check not res.hasErrors
test "unknown struct field":
let res = checkSource("struct Point { x: float64; y: float64; } func Main() -> int32 { let p = Point { x: 1.0, y: 2.0 }; return p.z; }")
check res.hasErrors
check "no field" in res.diagnostics[0].message
test "valid comparison":
let res = checkSource("func Main() -> int { return 1 == 2; }")
check not res.hasErrors
test "valid slice literal":
let res = checkSource("func Main() -> int { let arr = [1, 2, 3]; return 0; }")
check not res.hasErrors
test "slice element type mismatch":
let res = checkSource("func Main() -> int { let arr = [1, c8\"a\"]; return 0; }")
check res.hasErrors
test "method call with extend":
let src = """
struct Point { x: float64; y: float64; }
extend Point {
func Distance(self: Point) -> float64 { return 0.0; }
}
func Main() -> int {
let p = Point { x: 1.0, y: 2.0 };
let d = p.Distance();
return 0;
}
"""
let res = checkSource(src)
check not res.hasErrors
test "method call with wrong arguments":
let src = """
struct Point { x: float64; y: float64; }
extend Point {
func Add(self: Point, other: Point) -> Point { return self; }
}
func Main() -> int {
let p = Point { x: 1.0, y: 2.0 };
let q = p.Add();
return 0;
}
"""
let res = checkSource(src)
check res.hasErrors
check "too few arguments" in res.diagnostics[0].message
test "interface declaration":
let src = """
interface Display {
func ToString(self: Self) -> String;
}
"""
let res = checkSource(src)
check not res.hasErrors
test "extend for interface":
let src = """
struct Point { x: float64; y: float64; }
interface Display {
func ToString(self: Self) -> String;
}
extend Point for Display {
func ToString(self: Point) -> String { return c8"Point"; }
}
func Main() -> int { return 0; }
"""
let res = checkSource(src)
check not res.hasErrors
+15
View File
@@ -0,0 +1,15 @@
import "../bootstrap/lexer", "../bootstrap/parser", "../bootstrap/ast"
let source = readFile("../_selfhost/src/ast.bux")
let lexRes = tokenize(source, "ast.bux")
let res = parse(lexRes.tokens, "ast.bux")
if res.module != nil:
echo "Module items: ", res.module.items.len
for decl in res.module.items:
if decl.kind == dkStruct:
echo "Struct: ", decl.declStructName, " fields: ", decl.declStructFields.len
elif decl.kind == dkModule:
echo "Inner module: ", decl.declModuleName, " items: ", decl.declModuleItems.len
for inner in decl.declModuleItems:
if inner.kind == dkStruct:
echo " Struct: ", inner.declStructName, " fields: ", inner.declStructFields.len
+16
View File
@@ -0,0 +1,16 @@
import "../bootstrap/lexer", "../bootstrap/parser", "../bootstrap/ast", "../bootstrap/token"
import std/os
let source = readFile("../_selfhost/src/ast.bux")
let lexRes = tokenize(source, "ast.bux")
echo "Tokens: ", lexRes.tokens.len
echo "Errors: ", lexRes.hasErrors
let res = parse(lexRes.tokens, "ast.bux")
if res.module == nil:
echo "Parse failed, diagnostics: ", res.diagnostics.len
for d in res.diagnostics:
echo " ", d.message
else:
echo "Parsed OK, items: ", res.module.items.len
for i, decl in res.module.items:
echo "Decl ", i, " kind=", decl.kind, " name=", decl.declFuncName
+11
View File
@@ -0,0 +1,11 @@
import Std::Io::PrintLine;
func Add(a: int32, b: int32) -> int32 {
return a + b;
}
func Main() -> int {
let result: int32 = Add(10, 20);
PrintLine(c8"Hello from Bux!");
return 0;
}