v0.3.0: restructure directories
- src/ ← compiler/selfhost/ (canonical Bux compiler) - bootstrap/ ← compiler/bootstrap/ (Nim bootstrap) - lib/ ← library/std/ (standard library) - rt/ ← library/runtime/ (C runtime) - tests/ ← compiler/tests/ (unit tests) - Remove _selfhost/ (built into build/selfhost/ now) - Update all path references (Makefile, cli.nim, cli.bux, docs) - Bump version to 0.3.0
This commit is contained in:
@@ -1,443 +0,0 @@
|
||||
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)
|
||||
@@ -1,721 +0,0 @@
|
||||
import std/[strformat, strutils]
|
||||
import hir, types, token
|
||||
|
||||
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:
|
||||
when defined(release):
|
||||
return "void*"
|
||||
else:
|
||||
stderr.writeLine("warning: C backend: unknown type kind " & $typ.kind & ", using void*")
|
||||
return "void*"
|
||||
|
||||
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("")
|
||||
|
||||
# Forward declarations for all structs
|
||||
for s in module.structs:
|
||||
be.emitLine(&"typedef struct {s.name} {s.name};")
|
||||
if module.structs.len > 0:
|
||||
be.emitLine("")
|
||||
|
||||
# Enum definitions (must come before structs that reference them)
|
||||
for e in module.enums:
|
||||
be.emitEnum(e.name, e.variants)
|
||||
if module.enums.len > 0:
|
||||
be.emitLine("")
|
||||
|
||||
# Struct definitions
|
||||
for s in module.structs:
|
||||
be.emitStruct(s.name, s.fields)
|
||||
|
||||
# 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
|
||||
@@ -1,599 +0,0 @@
|
||||
import std/[os, strutils, terminal, strformat, osproc, sets]
|
||||
import lexer, parser, ast, sema, manifest, hir_lower, c_backend
|
||||
|
||||
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
|
||||
test Run tests in tests/ directory
|
||||
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 findStdlibDir(root: string): string =
|
||||
let searchPaths = @[
|
||||
getAppDir() / ".." / "library",
|
||||
getAppDir() / "library",
|
||||
root / "library",
|
||||
]
|
||||
for path in searchPaths:
|
||||
if dirExists(path):
|
||||
return path
|
||||
return ""
|
||||
|
||||
type
|
||||
ProjectContext = object
|
||||
root: string
|
||||
man: Manifest
|
||||
stdlibDir: string
|
||||
stdlibDecls: seq[Decl]
|
||||
depDecls: seq[Decl]
|
||||
allModuleItems: seq[Decl]
|
||||
hasMain: bool
|
||||
|
||||
proc prepareProject(root: string, useColor: bool, opts: GlobalOptions): (ProjectContext, int) =
|
||||
var pctx: ProjectContext
|
||||
pctx.root = root
|
||||
let manifestPath = root / "bux.toml"
|
||||
if not fileExists(manifestPath):
|
||||
printError("no bux.toml found", useColor)
|
||||
return (pctx, 1)
|
||||
pctx.man = loadManifest(manifestPath)
|
||||
let srcDir = root / "src"
|
||||
if not dirExists(srcDir):
|
||||
printError("no src/ directory found", useColor)
|
||||
return (pctx, 1)
|
||||
|
||||
pctx.stdlibDir = findStdlibDir(root)
|
||||
pctx.stdlibDecls = collectStdlibDecls(pctx.stdlibDir)
|
||||
let lock = loadLockfile(root / "bux.lock")
|
||||
pctx.depDecls = collectDepDecls(lock, root, opts)
|
||||
|
||||
pctx.allModuleItems = @[]
|
||||
pctx.hasMain = 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 (pctx, 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 (pctx, 1)
|
||||
for decl in parseRes.module.items:
|
||||
if decl.kind == dkModule:
|
||||
for sub in decl.declModuleItems:
|
||||
pctx.allModuleItems.add(sub)
|
||||
else:
|
||||
pctx.allModuleItems.add(decl)
|
||||
if splitFile(path).name == "Main":
|
||||
pctx.hasMain = true
|
||||
|
||||
if not pctx.hasMain:
|
||||
printError("no Main.bux found in src/", useColor)
|
||||
return (pctx, 1)
|
||||
|
||||
return (pctx, 0)
|
||||
|
||||
proc mergeProject(pctx: ProjectContext): Module =
|
||||
let stdlibAndDeps = mergeDecls(pctx.stdlibDecls, pctx.depDecls)
|
||||
let mergedItems = mergeDecls(stdlibAndDeps, pctx.allModuleItems)
|
||||
var unifiedModule = newModule("main")
|
||||
unifiedModule.items = mergedItems
|
||||
return unifiedModule
|
||||
|
||||
proc cmdCheck*(args: seq[string], opts: GlobalOptions): int =
|
||||
let useColor = shouldUseColor(opts)
|
||||
let root = if args.len > 0: absolutePath(args[0]) else: getCurrentDir()
|
||||
let (pctx, status) = prepareProject(root, useColor, opts)
|
||||
if status != 0:
|
||||
return status
|
||||
let unifiedModule = mergeProject(pctx)
|
||||
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 (pctx, status) = prepareProject(root, useColor, opts)
|
||||
if status != 0:
|
||||
return status
|
||||
|
||||
# Create build directory
|
||||
let buildDir = root / "build"
|
||||
if not dirExists(buildDir):
|
||||
createDir(buildDir)
|
||||
|
||||
let unifiedModule = mergeProject(pctx)
|
||||
|
||||
# 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 stdlibDir = pctx.stdlibDir
|
||||
let runtimeDst = buildDir / "runtime.c"
|
||||
let ioDst = buildDir / "io.c"
|
||||
if stdlibDir == "":
|
||||
printError("stdlib directory not found", useColor)
|
||||
return 1
|
||||
|
||||
let runtimeSrc = stdlibDir / "runtime" / "runtime.c"
|
||||
if fileExists(runtimeSrc):
|
||||
copyFile(runtimeSrc, runtimeDst)
|
||||
else:
|
||||
printError("runtime.c not found in library/runtime/", useColor)
|
||||
return 1
|
||||
|
||||
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 pctx.man.name != "": pctx.man.name else: "bux_out"
|
||||
let outputFile = buildDir / outputName
|
||||
let ccCmd = &"cc -O2 -pthread -o {outputFile} {cFile} {runtimeDst} {ioDst} -lm -lcrypto 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 root = if args.len > 0: absolutePath(args[0]) else: getCurrentDir()
|
||||
let buildRes = cmdBuild(args, opts)
|
||||
if buildRes != 0:
|
||||
return buildRes
|
||||
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 cmdTest*(args: seq[string], opts: GlobalOptions): int =
|
||||
let useColor = shouldUseColor(opts)
|
||||
let root = getCurrentDir()
|
||||
let testsDir = root / "tests"
|
||||
var testFiles: seq[string] = @[]
|
||||
if dirExists(testsDir):
|
||||
for kind, path in walkDir(testsDir):
|
||||
if kind == pcFile and path.endsWith(".bux"):
|
||||
testFiles.add(path)
|
||||
if testFiles.len == 0:
|
||||
printError("no tests found in tests/ directory", useColor)
|
||||
return 1
|
||||
var passed = 0
|
||||
var failed = 0
|
||||
for testFile in testFiles:
|
||||
let testName = splitFile(testFile).name
|
||||
let tmpDir = getTempDir() / "bux_test_" & testName
|
||||
removeDir(tmpDir)
|
||||
createDir(tmpDir / "src")
|
||||
copyFile(testFile, tmpDir / "src" / "Main.bux")
|
||||
writeFile(tmpDir / "bux.toml", "[package]\nname = \"" & testName & "\"\nversion = \"0.1.0\"\n")
|
||||
let buildRes = cmdBuild(@[tmpDir], opts)
|
||||
if buildRes != 0:
|
||||
printError(&" FAIL {testName} (build)", useColor)
|
||||
failed += 1
|
||||
continue
|
||||
var execFile = tmpDir / "build" / testName
|
||||
if not fileExists(execFile):
|
||||
execFile = tmpDir / "build" / "bux_out"
|
||||
let exitCode = execCmd(execFile)
|
||||
if exitCode == 0:
|
||||
printInfo(&" PASS {testName}", useColor)
|
||||
passed += 1
|
||||
else:
|
||||
printError(&" FAIL {testName} (exit {exitCode})", useColor)
|
||||
failed += 1
|
||||
removeDir(tmpDir)
|
||||
echo &"\nResults: {passed} passed, {failed} failed"
|
||||
return if failed > 0: 1 else: 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 "test": return cmdTest(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
|
||||
@@ -1,224 +0,0 @@
|
||||
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
@@ -1,619 +0,0 @@
|
||||
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")
|
||||
@@ -1,6 +0,0 @@
|
||||
import std/os
|
||||
import cli
|
||||
|
||||
when isMainModule:
|
||||
let args = commandLineParams()
|
||||
quit(runCli(args))
|
||||
@@ -1,274 +0,0 @@
|
||||
import std/[strutils, os, tables, 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
@@ -1,46 +0,0 @@
|
||||
import std/tables
|
||||
import types, ast
|
||||
|
||||
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
|
||||
isOwn*: bool ## true if declared as own T
|
||||
|
||||
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
@@ -1,8 +0,0 @@
|
||||
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
|
||||
@@ -1,336 +0,0 @@
|
||||
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
|
||||
@@ -1,232 +0,0 @@
|
||||
import std/[sequtils, strutils]
|
||||
import token
|
||||
|
||||
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
|
||||
@@ -1,431 +0,0 @@
|
||||
// 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 };
|
||||
}
|
||||
}
|
||||
@@ -1,689 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -1,761 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -1,740 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -1,816 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,95 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,753 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// 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 };
|
||||
}
|
||||
}
|
||||
@@ -1,322 +0,0 @@
|
||||
// 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";
|
||||
}
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
// 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 "?";
|
||||
}
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
import std/[unittest, os, strutils]
|
||||
import ../bootstrap/[lexer, parser, sema, types]
|
||||
|
||||
proc checkSource(source: string): tuple[hasErrors: bool, diagnostics: seq[SemaDiagnostic]] =
|
||||
let lexRes = lexer.tokenize(source, "test.bux")
|
||||
if lexRes.hasErrors:
|
||||
return (true, @[])
|
||||
let parseRes = parser.parse(lexRes.tokens, "test.bux")
|
||||
if parseRes.diagnostics.len > 0:
|
||||
return (true, @[])
|
||||
let (semaRes, _) = sema.analyzeFull(parseRes.module)
|
||||
return (semaRes.hasErrors, semaRes.diagnostics)
|
||||
|
||||
suite "Borrow Checker":
|
||||
test "@[Checked] function with &mut allows mutation":
|
||||
let res = checkSource("""
|
||||
@[Checked]
|
||||
func Inc(p: &mut int) {
|
||||
*p = *p + 1;
|
||||
}
|
||||
@[Checked]
|
||||
func Main() -> int {
|
||||
var x: int = 5;
|
||||
Inc(&x);
|
||||
return x;
|
||||
}
|
||||
""")
|
||||
check(not res.hasErrors)
|
||||
|
||||
test "@[Checked] function rejects write through &T":
|
||||
let res = checkSource("""
|
||||
@[Checked]
|
||||
func BadWrite(p: &int) {
|
||||
*p = 42;
|
||||
}
|
||||
@[Checked]
|
||||
func Main() -> int {
|
||||
var x: int = 5;
|
||||
BadWrite(&x);
|
||||
return x;
|
||||
}
|
||||
""")
|
||||
check(res.hasErrors)
|
||||
|
||||
test "unchecked function allows write through raw pointer":
|
||||
let res = checkSource("""
|
||||
func RawWrite(p: *int) {
|
||||
*p = 42;
|
||||
}
|
||||
func Main() -> int {
|
||||
var x: int = 5;
|
||||
RawWrite(&x);
|
||||
return x;
|
||||
}
|
||||
""")
|
||||
check(not res.hasErrors)
|
||||
|
||||
test "&T allows reading":
|
||||
let res = checkSource("""
|
||||
@[Checked]
|
||||
func Get(p: &int) -> int {
|
||||
return *p;
|
||||
}
|
||||
@[Checked]
|
||||
func Main() -> int {
|
||||
var x: int = 5;
|
||||
return Get(&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"))
|
||||
|
||||
test "@[Checked] allows reinitialization after move":
|
||||
let res = checkSource("""
|
||||
struct Box {
|
||||
value: int;
|
||||
}
|
||||
@[Checked]
|
||||
func Take(b: own Box) -> int {
|
||||
return b.value;
|
||||
}
|
||||
@[Checked]
|
||||
func Main() -> int {
|
||||
var b: own Box = Box { value: 1 };
|
||||
Take(b);
|
||||
b = Box { value: 2 };
|
||||
return b.value;
|
||||
}
|
||||
""")
|
||||
check(not res.hasErrors)
|
||||
|
||||
test "@[Checked] move in assignment":
|
||||
let res = checkSource("""
|
||||
struct Box {
|
||||
value: int;
|
||||
}
|
||||
@[Checked]
|
||||
func Main() -> int {
|
||||
var a: own Box = Box { value: 1 };
|
||||
var b: own Box = a;
|
||||
return a.value;
|
||||
}
|
||||
""")
|
||||
check(res.hasErrors)
|
||||
check(res.diagnostics[0].message.contains("moved"))
|
||||
|
||||
test "@[Checked] move in return":
|
||||
let res = checkSource("""
|
||||
struct Box {
|
||||
value: int;
|
||||
}
|
||||
@[Checked]
|
||||
func Give() -> own Box {
|
||||
var x: own Box = Box { value: 42 };
|
||||
return x;
|
||||
}
|
||||
@[Checked]
|
||||
func Main() -> int {
|
||||
let b: own Box = Give();
|
||||
return b.value;
|
||||
}
|
||||
""")
|
||||
check(not res.hasErrors)
|
||||
@@ -1,104 +0,0 @@
|
||||
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"
|
||||
@@ -1,124 +0,0 @@
|
||||
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
|
||||
@@ -1,122 +0,0 @@
|
||||
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
|
||||
@@ -1,139 +0,0 @@
|
||||
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
|
||||
@@ -1,15 +0,0 @@
|
||||
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
|
||||
@@ -1,16 +0,0 @@
|
||||
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
|
||||
Vendored
-11
@@ -1,11 +0,0 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user