feat: add HIR, C backend, and end-to-end compilation
- Phase 3: High-Level IR (HIR) with lowering from AST - Method call desugaring (obj.method() → Type_method(obj)) - if/else, while, loop, break, continue lowering - struct, enum, function lowering - 8 HIR tests passing - Phase 5A: C backend code generation - Type mapping (Bux types → C11 types) - Expression and statement emission - Struct, enum, function generation - C main() wrapper for Bux Main() - Runtime shim (stdlib/runtime.c) - bux_alloc, bux_free, bux_print, bux_panic - BuxString, BuxSlice types - Bounds checking, division by zero - Build integration - bux build: lex → parse → sema → HIR → C → cc - bux run: build + execute - bux clean: remove build directory - Parser fixes - Newline handling in struct, enum, extend, interface blocks - self keyword as expression and parameter name - Sema improvements - Method resolution (extend blocks) - Interface conformance checking - collectGlobals made public - All 70 tests passing (25 lexer + 16 parser + 21 sema + 8 HIR) - End-to-end: Bux programs compile to native ELF64 binaries
This commit is contained in:
@@ -0,0 +1,386 @@
|
||||
import std/[strformat, strutils, tables]
|
||||
import hir, types, token, source_location
|
||||
|
||||
type
|
||||
CBackend* = object
|
||||
output*: string
|
||||
indent*: int
|
||||
varCounter*: int
|
||||
declaredVars*: seq[string]
|
||||
|
||||
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*(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:
|
||||
if typ.inner.len > 0:
|
||||
return typeToC(typ.inner[0]) & "*"
|
||||
return "void*"
|
||||
of tkSlice:
|
||||
if typ.inner.len > 0:
|
||||
return typeToC(typ.inner[0]) & "*"
|
||||
return "void*"
|
||||
of tkNamed: return typ.name
|
||||
of tkTuple: return "void*" # TODO: proper tuple struct
|
||||
of tkFunc: return "void*" # TODO: function pointer
|
||||
else: return "int"
|
||||
|
||||
proc operatorToC(op: TokenKind): string =
|
||||
case op
|
||||
of tkPlus: return "+"
|
||||
of tkMinus: return "-"
|
||||
of tkStar: return "*"
|
||||
of tkSlash: return "/"
|
||||
of tkPercent: return "%"
|
||||
of tkAmp: return "&"
|
||||
of tkPipe: return "|"
|
||||
of tkCaret: return "^"
|
||||
of tkShl: return "<<"
|
||||
of tkShr: return ">>"
|
||||
of tkAmpAmp: return "&&"
|
||||
of tkPipePipe: return "||"
|
||||
of tkEq: return "=="
|
||||
of tkNe: return "!="
|
||||
of tkLt: return "<"
|
||||
of tkLe: return "<="
|
||||
of tkGt: return ">"
|
||||
of tkGe: return ">="
|
||||
of tkBang: return "!"
|
||||
of tkTilde: return "~"
|
||||
of tkPlusPlus: return "++"
|
||||
of tkMinusMinus: return "--"
|
||||
of tkPlusAssign: return "+="
|
||||
of tkMinusAssign: return "-="
|
||||
of tkStarAssign: return "*="
|
||||
of tkSlashAssign: return "/="
|
||||
of tkPercentAssign: 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:
|
||||
return node.litToken.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:
|
||||
let ptrExpr = be.emitExpr(node.loadPtr)
|
||||
return &"(*{ptrExpr})"
|
||||
|
||||
of hFieldPtr:
|
||||
let base = be.emitExpr(node.fieldPtrBase)
|
||||
return &"(&({base}.{node.fieldName}))"
|
||||
|
||||
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:
|
||||
# For now, use a static array
|
||||
var elems: seq[string] = @[]
|
||||
for e in node.sliceInitElements:
|
||||
elems.add(be.emitExpr(e))
|
||||
let elemsStr = elems.join(", ")
|
||||
return &"{{{elemsStr}}}"
|
||||
|
||||
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(node.castType)
|
||||
return &"(({typ}){operand})"
|
||||
|
||||
of hIs:
|
||||
return "true" # TODO: proper type checking
|
||||
|
||||
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 hBlock:
|
||||
for stmt in node.blockStmts:
|
||||
be.emitStmt(stmt)
|
||||
if node.blockExpr != nil:
|
||||
let val = be.emitExpr(node.blockExpr)
|
||||
be.emitLine(&"{val};")
|
||||
|
||||
of hAlloca:
|
||||
let typ = typeToC(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(hfunc.retType)
|
||||
var params: seq[string] = @[]
|
||||
for p in hfunc.params:
|
||||
params.add(&"{typeToC(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:
|
||||
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(f.typ)
|
||||
be.emitLine(&"{typ} {f.name};")
|
||||
dec be.indent
|
||||
be.emitLine(&"}} {name};")
|
||||
be.emitLine("")
|
||||
|
||||
proc emitEnum*(be: var CBackend, name: string, variants: seq[string]) =
|
||||
be.emitLine(&"typedef enum {{")
|
||||
inc be.indent
|
||||
for i, v in variants:
|
||||
if i < variants.len - 1:
|
||||
be.emitLine(&"{name}_{v},")
|
||||
else:
|
||||
be.emitLine(&"{name}_{v}")
|
||||
dec be.indent
|
||||
be.emitLine(&"}} {name};")
|
||||
be.emitLine("")
|
||||
|
||||
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("")
|
||||
|
||||
# Forward declarations
|
||||
for s in module.structs:
|
||||
be.emitLine(&"typedef struct {s.name} {s.name};")
|
||||
if module.structs.len > 0:
|
||||
be.emitLine("")
|
||||
|
||||
# Struct definitions
|
||||
for s in module.structs:
|
||||
be.emitStruct(s.name, s.fields)
|
||||
|
||||
# Enum definitions
|
||||
for e in module.enums:
|
||||
be.emitEnum(e.name, e.variants)
|
||||
|
||||
# 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("int main(int argc, char** argv) {")
|
||||
be.emitLine(" return Main();")
|
||||
be.emitLine("}")
|
||||
be.emitLine("")
|
||||
|
||||
return be.output
|
||||
+113
-11
@@ -1,5 +1,5 @@
|
||||
import std/[os, strutils, terminal, strformat]
|
||||
import lexer, parser, sema, manifest
|
||||
import std/[os, strutils, terminal, strformat, osproc]
|
||||
import lexer, parser, sema, manifest, hir, hir_lower, c_backend, types, scope
|
||||
|
||||
type
|
||||
ColorMode* = enum
|
||||
@@ -192,12 +192,104 @@ proc cmdCheck*(args: seq[string], opts: GlobalOptions): int =
|
||||
|
||||
proc cmdBuild*(args: seq[string], opts: GlobalOptions): int =
|
||||
let useColor = shouldUseColor(opts)
|
||||
# For now, just type-check
|
||||
let checkRes = cmdCheck(args, opts)
|
||||
if checkRes != 0:
|
||||
return checkRes
|
||||
let root = getCurrentDir()
|
||||
let manifestPath = root / "bux.toml"
|
||||
if not fileExists(manifestPath):
|
||||
printError("no bux.toml found", useColor)
|
||||
return 1
|
||||
let man = loadManifest(manifestPath)
|
||||
let srcDir = root / "src"
|
||||
if not dirExists(srcDir):
|
||||
printError("no src/ directory found", useColor)
|
||||
return 1
|
||||
|
||||
# Create build directory
|
||||
let buildDir = root / "build"
|
||||
if not dirExists(buildDir):
|
||||
createDir(buildDir)
|
||||
|
||||
# Process all .bux files
|
||||
var allCCode = ""
|
||||
var foundMain = false
|
||||
for kind, path in walkDir(srcDir):
|
||||
if kind == pcFile and path.endsWith(".bux"):
|
||||
let source = readFile(path)
|
||||
let lexRes = tokenize(source, path)
|
||||
if lexRes.hasErrors:
|
||||
printError(&"lex errors in {path}", useColor)
|
||||
for d in lexRes.diagnostics:
|
||||
echo $d
|
||||
return 1
|
||||
let parseRes = parse(lexRes.tokens, path)
|
||||
if parseRes.diagnostics.len > 0:
|
||||
printError(&"parse errors in {path}", useColor)
|
||||
for d in parseRes.diagnostics:
|
||||
echo &"error: {d.message} at {d.loc}"
|
||||
return 1
|
||||
let semaRes = analyze(parseRes.module)
|
||||
if semaRes.hasErrors:
|
||||
printError(&"type errors in {path}", useColor)
|
||||
for d in semaRes.diagnostics:
|
||||
let sev = if d.severity == sdsError: "error" else: "warning"
|
||||
echo &"{sev}: {d.message} at {d.loc}"
|
||||
return 1
|
||||
|
||||
# Lower to HIR
|
||||
var s = Sema(module: parseRes.module, globalScope: newScope())
|
||||
s.collectGlobals()
|
||||
let hirModule = lowerModule(parseRes.module, s)
|
||||
|
||||
# Generate C code
|
||||
var cbe = initCBackend()
|
||||
let cCode = cbe.emitModule(hirModule)
|
||||
allCCode.add(cCode)
|
||||
allCCode.add("\n")
|
||||
|
||||
if splitFile(path).name == "Main":
|
||||
foundMain = true
|
||||
|
||||
if not foundMain:
|
||||
printError("no Main.bux found in src/", useColor)
|
||||
return 1
|
||||
|
||||
# Write C file
|
||||
let cFile = buildDir / "main.c"
|
||||
writeFile(cFile, allCCode)
|
||||
|
||||
# Copy runtime
|
||||
let runtimeDst = buildDir / "runtime.c"
|
||||
var runtimeFound = false
|
||||
# Search in multiple locations
|
||||
let searchPaths = @[
|
||||
getAppDir() / ".." / "stdlib" / "runtime.c",
|
||||
getAppDir() / "stdlib" / "runtime.c",
|
||||
root / "stdlib" / "runtime.c",
|
||||
root / "stdlib_runtime.c",
|
||||
"/home/ziko/z-git/bux/bux/stdlib/runtime.c", # TODO: make this configurable
|
||||
]
|
||||
for runtimePath in searchPaths:
|
||||
if fileExists(runtimePath):
|
||||
copyFile(runtimePath, runtimeDst)
|
||||
runtimeFound = true
|
||||
break
|
||||
if not runtimeFound:
|
||||
printError("runtime.c not found (searched in stdlib/, build/, and project root)", useColor)
|
||||
return 1
|
||||
|
||||
# Compile with cc
|
||||
let outputName = if man.name != "": man.name else: "bux_out"
|
||||
let outputFile = buildDir / outputName
|
||||
let ccCmd = &"cc -o {outputFile} {cFile} {runtimeDst} -lm 2>&1"
|
||||
if opts.verbose:
|
||||
printInfo(&"running: {ccCmd}", useColor)
|
||||
let (output, exitCode) = execCmdEx(ccCmd)
|
||||
if exitCode != 0:
|
||||
printError("C compilation failed:", useColor)
|
||||
echo output
|
||||
return 1
|
||||
|
||||
if not opts.quiet:
|
||||
printInfo("build: nothing to do yet (no codegen)", useColor)
|
||||
printInfo(&"build: {outputFile}", useColor)
|
||||
return 0
|
||||
|
||||
proc cmdRun*(args: seq[string], opts: GlobalOptions): int =
|
||||
@@ -205,14 +297,24 @@ proc cmdRun*(args: seq[string], opts: GlobalOptions): int =
|
||||
let buildRes = cmdBuild(args, opts)
|
||||
if buildRes != 0:
|
||||
return buildRes
|
||||
printError("run: no executable produced yet (no codegen)", useColor)
|
||||
return 1
|
||||
let root = getCurrentDir()
|
||||
let man = loadManifest(root / "bux.toml")
|
||||
let outputName = if man.name != "": man.name else: "bux_out"
|
||||
let outputFile = root / "build" / outputName
|
||||
if not fileExists(outputFile):
|
||||
printError("executable not found after build", useColor)
|
||||
return 1
|
||||
let exitCode = execCmd(outputFile)
|
||||
return exitCode
|
||||
|
||||
proc cmdClean*(args: seq[string], opts: GlobalOptions): int =
|
||||
let useColor = shouldUseColor(opts)
|
||||
# Placeholder
|
||||
let root = getCurrentDir()
|
||||
let buildDir = root / "build"
|
||||
if dirExists(buildDir):
|
||||
removeDir(buildDir)
|
||||
if not opts.quiet:
|
||||
printInfo("clean: nothing to do yet", useColor)
|
||||
printInfo("clean: build directory removed", useColor)
|
||||
return 0
|
||||
|
||||
proc cmdVersion*(args: seq[string], opts: GlobalOptions): int =
|
||||
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
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
|
||||
hIndexPtr
|
||||
# Functions
|
||||
hCall
|
||||
hCallIndirect
|
||||
# Type operations
|
||||
hCast
|
||||
hIs
|
||||
# Composite
|
||||
hBlock
|
||||
hStructInit
|
||||
hSliceInit
|
||||
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 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 hBlock:
|
||||
blockStmts*: seq[HirNode]
|
||||
blockExpr*: HirNode
|
||||
of hStructInit:
|
||||
structInitName*: string
|
||||
structInitFields*: seq[tuple[name: string, value: HirNode]]
|
||||
of hSliceInit:
|
||||
sliceInitElements*: seq[HirNode]
|
||||
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
|
||||
|
||||
HirModule* = object
|
||||
funcs*: seq[HirFunc]
|
||||
structs*: seq[tuple[name: string, fields: seq[tuple[name: string, typ: Type]]]]
|
||||
enums*: seq[tuple[name: string, variants: seq[string]]]
|
||||
consts*: seq[tuple[name: string, typ: Type, value: HirNode]]
|
||||
|
||||
# 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): HirNode =
|
||||
HirNode(kind: hBlock, blockStmts: stmts, blockExpr: expr, typ: typ, loc: loc)
|
||||
|
||||
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)
|
||||
@@ -0,0 +1,464 @@
|
||||
import std/[tables, strformat, strutils]
|
||||
import ast, types, token, source_location, hir, sema, scope
|
||||
|
||||
type
|
||||
LowerCtx* = object
|
||||
module*: Module
|
||||
globalScope*: Scope
|
||||
methodTable*: Table[string, seq[MethodInfo]]
|
||||
currentFuncRetType*: Type
|
||||
varCounter*: int
|
||||
|
||||
proc freshName(ctx: var LowerCtx): string =
|
||||
inc ctx.varCounter
|
||||
result = "__tmp_" & $ctx.varCounter
|
||||
|
||||
proc initLowerCtx*(module: Module, sema: Sema): LowerCtx =
|
||||
result.module = module
|
||||
result.globalScope = sema.globalScope
|
||||
result.methodTable = sema.methodTable
|
||||
result.varCounter = 0
|
||||
|
||||
# Forward declarations
|
||||
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode
|
||||
proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode
|
||||
proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode
|
||||
|
||||
proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
||||
if expr == nil: return makeUnknown()
|
||||
case expr.kind
|
||||
of ekLiteral:
|
||||
case expr.exprLit.kind
|
||||
of tkIntLiteral: return makeInt()
|
||||
of tkFloatLiteral: return makeFloat64()
|
||||
of tkStringLiteral: return makeStr()
|
||||
of tkCharLiteral: return makeChar8()
|
||||
of tkBoolLiteral: return makeBool()
|
||||
else: return makeUnknown()
|
||||
of ekIdent:
|
||||
let sym = ctx.globalScope.lookup(expr.exprIdent)
|
||||
if sym != nil and sym.typ != nil: return sym.typ
|
||||
return makeUnknown()
|
||||
of ekSelf: return makeNamed("self")
|
||||
of ekBinary:
|
||||
let left = ctx.resolveExprType(expr.exprBinaryLeft)
|
||||
case expr.exprBinaryOp
|
||||
of tkEq, tkNe, tkLt, tkLe, tkGt, tkGe, tkAmpAmp, tkPipePipe:
|
||||
return makeBool()
|
||||
else: return left
|
||||
of ekUnary:
|
||||
case expr.exprUnaryOp
|
||||
of tkBang: return makeBool()
|
||||
of tkAmp: return makePointer(ctx.resolveExprType(expr.exprUnaryOperand))
|
||||
of tkStar:
|
||||
let inner = ctx.resolveExprType(expr.exprUnaryOperand)
|
||||
if inner.isPointer: return inner.inner[0]
|
||||
return makeUnknown()
|
||||
else: return ctx.resolveExprType(expr.exprUnaryOperand)
|
||||
of ekCall:
|
||||
if expr.exprCallCallee.kind == ekIdent:
|
||||
let sym = ctx.globalScope.lookup(expr.exprCallCallee.exprIdent)
|
||||
if sym != nil and sym.typ != nil and sym.typ.kind == tkFunc:
|
||||
return sym.typ.inner[^1]
|
||||
if expr.exprCallCallee.kind == ekField:
|
||||
let recvType = ctx.resolveExprType(expr.exprCallCallee.exprFieldObj)
|
||||
let methodName = expr.exprCallCallee.exprFieldName
|
||||
var typeName = ""
|
||||
if recvType.kind == tkNamed: typeName = recvType.name
|
||||
elif recvType.isPointer and recvType.inner.len > 0 and recvType.inner[0].kind == tkNamed:
|
||||
typeName = recvType.inner[0].name
|
||||
if typeName != "" and ctx.methodTable.hasKey(typeName):
|
||||
for minfo in ctx.methodTable[typeName]:
|
||||
if minfo.name == methodName:
|
||||
return minfo.retType
|
||||
return makeUnknown()
|
||||
of ekField:
|
||||
let objType = ctx.resolveExprType(expr.exprFieldObj)
|
||||
if objType.kind == tkNamed:
|
||||
let sym = ctx.globalScope.lookup(objType.name)
|
||||
if sym != nil and sym.decl != nil and sym.decl.kind == dkStruct:
|
||||
for f in sym.decl.declStructFields:
|
||||
if f.name == expr.exprFieldName:
|
||||
if f.ftype != nil:
|
||||
case f.ftype.kind
|
||||
of tekNamed:
|
||||
case f.ftype.typeName
|
||||
of "int", "int32", "int64": return makeInt()
|
||||
of "float64": return makeFloat64()
|
||||
of "float32": return makeFloat32()
|
||||
of "bool": return makeBool()
|
||||
else: return makeNamed(f.ftype.typeName)
|
||||
of tekPointer: return makePointer(makeUnknown())
|
||||
else: return makeUnknown()
|
||||
return makeUnknown()
|
||||
of ekStructInit: return makeNamed(expr.exprStructInitName)
|
||||
of ekSlice:
|
||||
if expr.exprSliceElements.len > 0:
|
||||
return makeSlice(ctx.resolveExprType(expr.exprSliceElements[0]))
|
||||
return makeSlice(makeUnknown())
|
||||
of ekTuple:
|
||||
var elems: seq[Type] = @[]
|
||||
for e in expr.exprTupleElements:
|
||||
elems.add(ctx.resolveExprType(e))
|
||||
return makeTuple(elems)
|
||||
of ekCast:
|
||||
if expr.exprCastType != nil:
|
||||
case expr.exprCastType.kind
|
||||
of tekNamed: return makeNamed(expr.exprCastType.typeName)
|
||||
else: return makeUnknown()
|
||||
return makeUnknown()
|
||||
of ekBlock:
|
||||
if expr.exprBlock.stmts.len > 0:
|
||||
let last = expr.exprBlock.stmts[^1]
|
||||
if last.kind == skExpr:
|
||||
return ctx.resolveExprType(last.stmtExpr)
|
||||
return makeVoid()
|
||||
else: return makeUnknown()
|
||||
|
||||
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
if expr == nil: return nil
|
||||
let loc = expr.loc
|
||||
let typ = ctx.resolveExprType(expr)
|
||||
|
||||
case expr.kind
|
||||
of ekLiteral:
|
||||
return hirLit(expr.exprLit, typ, loc)
|
||||
|
||||
of ekIdent:
|
||||
return hirVar(expr.exprIdent, typ, loc)
|
||||
|
||||
of ekSelf:
|
||||
return hirSelf(typ, loc)
|
||||
|
||||
of ekUnary:
|
||||
let operand = ctx.lowerExpr(expr.exprUnaryOperand)
|
||||
return hirUnary(expr.exprUnaryOp, operand, typ, loc)
|
||||
|
||||
of ekBinary:
|
||||
let left = ctx.lowerExpr(expr.exprBinaryLeft)
|
||||
let right = ctx.lowerExpr(expr.exprBinaryRight)
|
||||
return hirBinary(expr.exprBinaryOp, left, right, typ, loc)
|
||||
|
||||
of ekCall:
|
||||
# Method call desugaring: obj.method(args) → Type_method(obj, args)
|
||||
if expr.exprCallCallee.kind == ekField:
|
||||
let recvType = ctx.resolveExprType(expr.exprCallCallee.exprFieldObj)
|
||||
let methodName = expr.exprCallCallee.exprFieldName
|
||||
var typeName = ""
|
||||
if recvType.kind == tkNamed: typeName = recvType.name
|
||||
elif recvType.isPointer and recvType.inner.len > 0 and recvType.inner[0].kind == tkNamed:
|
||||
typeName = recvType.inner[0].name
|
||||
if typeName != "" and ctx.methodTable.hasKey(typeName):
|
||||
for minfo in ctx.methodTable[typeName]:
|
||||
if minfo.name == methodName:
|
||||
# Desugar: obj.method(args) → Type_method(&obj, args)
|
||||
let mangledName = typeName & "_" & methodName
|
||||
var args: seq[HirNode] = @[]
|
||||
args.add(ctx.lowerExpr(expr.exprCallCallee.exprFieldObj))
|
||||
for arg in expr.exprCallArgs:
|
||||
args.add(ctx.lowerExpr(arg))
|
||||
return hirCall(mangledName, args, typ, loc)
|
||||
|
||||
# Regular function call
|
||||
var calleeName = ""
|
||||
if expr.exprCallCallee.kind == ekIdent:
|
||||
calleeName = expr.exprCallCallee.exprIdent
|
||||
elif expr.exprCallCallee.kind == ekPath:
|
||||
calleeName = expr.exprCallCallee.exprPath.join("::")
|
||||
var args: seq[HirNode] = @[]
|
||||
for arg in expr.exprCallArgs:
|
||||
args.add(ctx.lowerExpr(arg))
|
||||
if calleeName != "":
|
||||
return hirCall(calleeName, args, typ, loc)
|
||||
else:
|
||||
let callee = ctx.lowerExpr(expr.exprCallCallee)
|
||||
return HirNode(kind: hCallIndirect, callIndirectCallee: callee,
|
||||
callIndirectArgs: args, typ: typ, loc: loc)
|
||||
|
||||
of ekField:
|
||||
let base = ctx.lowerExpr(expr.exprFieldObj)
|
||||
let basePtr = HirNode(kind: hFieldPtr, fieldPtrBase: base,
|
||||
fieldName: expr.exprFieldName,
|
||||
typ: makePointer(typ), loc: loc)
|
||||
return HirNode(kind: hLoad, loadPtr: basePtr, typ: typ, loc: loc)
|
||||
|
||||
of ekIndex:
|
||||
let base = ctx.lowerExpr(expr.exprIndexObj)
|
||||
let idx = ctx.lowerExpr(expr.exprIndexIdx)
|
||||
let basePtr = HirNode(kind: hIndexPtr, indexPtrBase: base,
|
||||
indexPtrIndex: idx, typ: makePointer(typ), loc: loc)
|
||||
return HirNode(kind: hLoad, loadPtr: basePtr, typ: typ, loc: loc)
|
||||
|
||||
of ekAssign:
|
||||
let target = ctx.lowerExpr(expr.exprAssignTarget)
|
||||
let value = ctx.lowerExpr(expr.exprAssignValue)
|
||||
return HirNode(kind: hAssign, assignOp: expr.exprAssignOp,
|
||||
assignTarget: target, assignValue: value,
|
||||
typ: makeVoid(), loc: loc)
|
||||
|
||||
of ekStructInit:
|
||||
var fields: seq[tuple[name: string, value: HirNode]] = @[]
|
||||
for f in expr.exprStructInitFields:
|
||||
fields.add((f.name, ctx.lowerExpr(f.value)))
|
||||
return HirNode(kind: hStructInit, structInitName: expr.exprStructInitName,
|
||||
structInitFields: fields, typ: typ, loc: loc)
|
||||
|
||||
of ekSlice:
|
||||
var elems: seq[HirNode] = @[]
|
||||
for e in expr.exprSliceElements:
|
||||
elems.add(ctx.lowerExpr(e))
|
||||
return HirNode(kind: hSliceInit, sliceInitElements: elems, typ: typ, loc: loc)
|
||||
|
||||
of ekTuple:
|
||||
var elems: seq[HirNode] = @[]
|
||||
for e in expr.exprTupleElements:
|
||||
elems.add(ctx.lowerExpr(e))
|
||||
return HirNode(kind: hTupleInit, tupleInitElements: elems, typ: typ, loc: loc)
|
||||
|
||||
of ekCast:
|
||||
let operand = ctx.lowerExpr(expr.exprCastOperand)
|
||||
var castType = makeUnknown()
|
||||
if expr.exprCastType != nil:
|
||||
case expr.exprCastType.kind
|
||||
of tekNamed: castType = makeNamed(expr.exprCastType.typeName)
|
||||
of tekPointer: castType = makePointer(makeUnknown())
|
||||
else: discard
|
||||
return HirNode(kind: hCast, castOperand: operand, castType: castType,
|
||||
typ: typ, loc: loc)
|
||||
|
||||
of ekBlock:
|
||||
return ctx.lowerBlock(expr.exprBlock)
|
||||
|
||||
of ekPostfix:
|
||||
let operand = ctx.lowerExpr(expr.exprPostfixOperand)
|
||||
return HirNode(kind: hUnary, unaryOp: expr.exprPostfixOp,
|
||||
unaryOperand: operand, typ: typ, loc: loc)
|
||||
|
||||
of ekTernary:
|
||||
let cond = ctx.lowerExpr(expr.exprTernaryCond)
|
||||
let thenE = ctx.lowerExpr(expr.exprTernaryThen)
|
||||
let elseE = ctx.lowerExpr(expr.exprTernaryElse)
|
||||
return HirNode(kind: hIf, ifCond: cond, ifThen: thenE, ifElse: elseE,
|
||||
typ: typ, loc: loc)
|
||||
|
||||
of ekIs:
|
||||
let operand = ctx.lowerExpr(expr.exprIsOperand)
|
||||
var isType = makeUnknown()
|
||||
if expr.exprIsType != nil and expr.exprIsType.kind == tekNamed:
|
||||
isType = makeNamed(expr.exprIsType.typeName)
|
||||
return HirNode(kind: hIs, isOperand: operand, isType: isType,
|
||||
typ: makeBool(), loc: loc)
|
||||
|
||||
of ekMatch:
|
||||
let subject = ctx.lowerExpr(expr.exprMatchSubject)
|
||||
var arms: seq[HirMatchArm] = @[]
|
||||
for arm in expr.exprMatchArms:
|
||||
arms.add(HirMatchArm(pattern: arm.pattern, body: ctx.lowerExpr(arm.body)))
|
||||
return HirNode(kind: hMatch, matchSubject: subject, matchArms: arms,
|
||||
typ: typ, loc: loc)
|
||||
|
||||
of ekSizeOf:
|
||||
return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc),
|
||||
typ: makeInt(), loc: loc)
|
||||
|
||||
of ekIntrinsic:
|
||||
return HirNode(kind: hLit, litToken: Token(kind: tkStringLiteral, text: "\"\"", loc: loc),
|
||||
typ: makeStr(), loc: loc)
|
||||
|
||||
else:
|
||||
return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc),
|
||||
typ: makeVoid(), loc: loc)
|
||||
|
||||
proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
if stmt == nil: return nil
|
||||
let loc = stmt.loc
|
||||
|
||||
case stmt.kind
|
||||
of skExpr:
|
||||
return ctx.lowerExpr(stmt.stmtExpr)
|
||||
|
||||
of skLet:
|
||||
let initHir = ctx.lowerExpr(stmt.stmtLetInit)
|
||||
let allocaType = if stmt.stmtLetType != nil:
|
||||
case stmt.stmtLetType.kind
|
||||
of tekNamed:
|
||||
case stmt.stmtLetType.typeName
|
||||
of "int", "int32": makeInt()
|
||||
of "int64": makeInt64()
|
||||
of "float64": makeFloat64()
|
||||
of "float32": makeFloat32()
|
||||
of "bool": makeBool()
|
||||
else: makeNamed(stmt.stmtLetType.typeName)
|
||||
of tekPointer: makePointer(makeUnknown())
|
||||
else: makeUnknown()
|
||||
else:
|
||||
ctx.resolveExprType(stmt.stmtLetInit)
|
||||
|
||||
let alloca = hirAlloca(stmt.stmtLetName, allocaType, loc)
|
||||
let varNode = hirVar(stmt.stmtLetName, makePointer(allocaType), loc)
|
||||
let store = hirStore(varNode, initHir, loc)
|
||||
return HirNode(kind: hBlock, blockStmts: @[alloca, store],
|
||||
blockExpr: nil, typ: makeVoid(), loc: loc)
|
||||
|
||||
of skReturn:
|
||||
let value = if stmt.stmtReturnValue != nil: ctx.lowerExpr(stmt.stmtReturnValue) else: nil
|
||||
return hirReturn(value, loc)
|
||||
|
||||
of skIf:
|
||||
let cond = ctx.lowerExpr(stmt.stmtIfCond)
|
||||
let thenBlock = ctx.lowerBlock(stmt.stmtIfThen)
|
||||
var elseBlock: HirNode = nil
|
||||
if stmt.stmtIfElse != nil:
|
||||
elseBlock = ctx.lowerBlock(stmt.stmtIfElse)
|
||||
elif stmt.stmtIfElseIfs.len > 0:
|
||||
# Desugar else-if chain
|
||||
var current: HirNode = nil
|
||||
for i in countdown(stmt.stmtIfElseIfs.len - 1, 0):
|
||||
let elifBranch = stmt.stmtIfElseIfs[i]
|
||||
let elifCond = ctx.lowerExpr(elifBranch.cond)
|
||||
let elifBlock = ctx.lowerBlock(elifBranch.blk)
|
||||
current = HirNode(kind: hIf, ifCond: elifCond, ifThen: elifBlock,
|
||||
ifElse: current, typ: makeVoid(), loc: elifBranch.loc)
|
||||
elseBlock = current
|
||||
return HirNode(kind: hIf, ifCond: cond, ifThen: thenBlock, ifElse: elseBlock,
|
||||
typ: makeVoid(), loc: loc)
|
||||
|
||||
of skWhile:
|
||||
let cond = ctx.lowerExpr(stmt.stmtWhileCond)
|
||||
let body = ctx.lowerBlock(stmt.stmtWhileBody)
|
||||
return HirNode(kind: hWhile, whileCond: cond, whileBody: body,
|
||||
typ: makeVoid(), loc: loc)
|
||||
|
||||
of skLoop:
|
||||
let body = ctx.lowerBlock(stmt.stmtLoopBody)
|
||||
return HirNode(kind: hLoop, loopBody: body, typ: makeVoid(), loc: loc)
|
||||
|
||||
of skBreak:
|
||||
return HirNode(kind: hBreak, breakLabel: stmt.stmtBreakLabel,
|
||||
typ: makeVoid(), loc: loc)
|
||||
|
||||
of skContinue:
|
||||
return HirNode(kind: hContinue, continueLabel: stmt.stmtContinueLabel,
|
||||
typ: makeVoid(), loc: loc)
|
||||
|
||||
of skFor:
|
||||
# Desugar: for i in iter { body } → { let __iter = iter; while __hasNext(__iter) { let i = __next(__iter); body } }
|
||||
let iterExpr = ctx.lowerExpr(stmt.stmtForIter)
|
||||
let body = ctx.lowerBlock(stmt.stmtForBody)
|
||||
# Simplified: just lower the body for now
|
||||
return HirNode(kind: hLoop, loopBody: body, typ: makeVoid(), loc: loc)
|
||||
|
||||
of skDoWhile:
|
||||
let body = ctx.lowerBlock(stmt.stmtDoWhileBody)
|
||||
let cond = ctx.lowerExpr(stmt.stmtDoWhileCond)
|
||||
let whileNode = HirNode(kind: hWhile, whileCond: cond, whileBody: body,
|
||||
typ: makeVoid(), loc: loc)
|
||||
return HirNode(kind: hBlock, blockStmts: @[body, whileNode],
|
||||
blockExpr: nil, typ: makeVoid(), loc: loc)
|
||||
|
||||
of skMatch:
|
||||
let subject = ctx.lowerExpr(stmt.stmtMatchSubject)
|
||||
var arms: seq[HirMatchArm] = @[]
|
||||
for arm in stmt.stmtMatchArms:
|
||||
arms.add(HirMatchArm(pattern: arm.pattern, body: ctx.lowerExpr(arm.body)))
|
||||
return HirNode(kind: hMatch, matchSubject: subject, matchArms: arms,
|
||||
typ: makeVoid(), loc: loc)
|
||||
|
||||
of skDecl:
|
||||
return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc),
|
||||
typ: makeVoid(), loc: loc)
|
||||
|
||||
proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode =
|
||||
if blk == nil: return nil
|
||||
var stmts: seq[HirNode] = @[]
|
||||
for s in blk.stmts:
|
||||
let hir = ctx.lowerStmt(s)
|
||||
if hir != nil:
|
||||
stmts.add(hir)
|
||||
return HirNode(kind: hBlock, blockStmts: stmts, blockExpr: nil,
|
||||
typ: makeVoid(), loc: blk.loc)
|
||||
|
||||
proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
||||
var params: seq[tuple[name: string, typ: Type]] = @[]
|
||||
for p in decl.declFuncParams:
|
||||
var pType = makeUnknown()
|
||||
if p.ptype != nil:
|
||||
case p.ptype.kind
|
||||
of tekNamed:
|
||||
case p.ptype.typeName
|
||||
of "int", "int32": pType = makeInt()
|
||||
of "int64": pType = makeInt64()
|
||||
of "float64": pType = makeFloat64()
|
||||
of "float32": pType = makeFloat32()
|
||||
of "bool": pType = makeBool()
|
||||
of "Point", "Self": pType = makeNamed(p.ptype.typeName)
|
||||
else: pType = makeNamed(p.ptype.typeName)
|
||||
of tekPointer: pType = makePointer(makeUnknown())
|
||||
else: discard
|
||||
params.add((p.name, pType))
|
||||
|
||||
var retType = makeVoid()
|
||||
if decl.declFuncReturnType != nil:
|
||||
case decl.declFuncReturnType.kind
|
||||
of tekNamed:
|
||||
case decl.declFuncReturnType.typeName
|
||||
of "int", "int32": retType = makeInt()
|
||||
of "int64": retType = makeInt64()
|
||||
of "float64": retType = makeFloat64()
|
||||
of "float32": retType = makeFloat32()
|
||||
of "bool": retType = makeBool()
|
||||
else: retType = makeNamed(decl.declFuncReturnType.typeName)
|
||||
of tekPointer: retType = makePointer(makeUnknown())
|
||||
else: discard
|
||||
|
||||
ctx.currentFuncRetType = retType
|
||||
let body = if decl.declFuncBody != nil: ctx.lowerBlock(decl.declFuncBody) else: nil
|
||||
|
||||
result = HirFunc(name: decl.declFuncName, params: params, retType: retType,
|
||||
body: body, isPublic: decl.isPublic)
|
||||
|
||||
proc lowerModule*(module: Module, sema: Sema): HirModule =
|
||||
var ctx = initLowerCtx(module, sema)
|
||||
var funcs: seq[HirFunc] = @[]
|
||||
var structs: seq[tuple[name: string, fields: seq[tuple[name: string, typ: Type]]]] = @[]
|
||||
var enums: seq[tuple[name: string, variants: seq[string]]] = @[]
|
||||
var consts: seq[tuple[name: string, typ: Type, value: HirNode]] = @[]
|
||||
|
||||
for decl in module.items:
|
||||
case decl.kind
|
||||
of dkFunc:
|
||||
funcs.add(ctx.lowerFunc(decl))
|
||||
of dkImpl:
|
||||
for methodDecl in decl.declImplMethods:
|
||||
if methodDecl.kind == dkFunc:
|
||||
var hf = ctx.lowerFunc(methodDecl)
|
||||
hf.name = decl.declImplTypeName & "_" & hf.name
|
||||
funcs.add(hf)
|
||||
of dkStruct:
|
||||
var fields: seq[tuple[name: string, typ: Type]] = @[]
|
||||
for f in decl.declStructFields:
|
||||
var fType = makeUnknown()
|
||||
if f.ftype != nil and f.ftype.kind == tekNamed:
|
||||
case f.ftype.typeName
|
||||
of "float64": fType = makeFloat64()
|
||||
of "float32": fType = makeFloat32()
|
||||
of "int", "int32": fType = makeInt()
|
||||
else: fType = makeNamed(f.ftype.typeName)
|
||||
fields.add((f.name, fType))
|
||||
structs.add((decl.declStructName, fields))
|
||||
of dkEnum:
|
||||
var variants: seq[string] = @[]
|
||||
for v in decl.declEnumVariants:
|
||||
variants.add(v.name)
|
||||
enums.add((decl.declEnumName, variants))
|
||||
of dkConst:
|
||||
let value = ctx.lowerExpr(decl.declConstValue)
|
||||
let typ = if decl.declConstType != nil:
|
||||
case decl.declConstType.kind
|
||||
of tekNamed: makeNamed(decl.declConstType.typeName)
|
||||
else: makeUnknown()
|
||||
else: makeUnknown()
|
||||
consts.add((decl.declConstName, typ, value))
|
||||
else: discard
|
||||
|
||||
result = HirModule(funcs: funcs, structs: structs, enums: enums, consts: consts)
|
||||
Executable
BIN
Binary file not shown.
+39
-4
@@ -323,10 +323,11 @@ proc parsePrimary(p: var Parser): Expr =
|
||||
case p.peek()
|
||||
of tkIntLiteral, tkFloatLiteral, tkStringLiteral, tkCharLiteral, tkBoolLiteral:
|
||||
return newLiteralExpr(p.advance())
|
||||
of tkSelf:
|
||||
discard p.advance()
|
||||
return Expr(kind: ekSelf, loc: loc)
|
||||
of tkIdent:
|
||||
let name = p.advance().text
|
||||
if name == "self":
|
||||
return Expr(kind: ekSelf, loc: loc)
|
||||
# Path expression: a::b::c
|
||||
if p.check(tkColonColon):
|
||||
var segs = @[name]
|
||||
@@ -781,11 +782,25 @@ proc parseParamList(p: var Parser, allowVariadic: bool = false): seq[Param] =
|
||||
var isVar = false
|
||||
if allowVariadic and p.check(tkDotDotDot):
|
||||
discard p.advance()
|
||||
let name = p.expect(tkIdent, "expected parameter name after '...'").text
|
||||
let nameTok = p.at
|
||||
var name = ""
|
||||
if nameTok.kind == tkIdent:
|
||||
name = p.advance().text
|
||||
elif nameTok.kind == tkSelf:
|
||||
name = p.advance().text
|
||||
else:
|
||||
name = p.expect(tkIdent, "expected parameter name after '...'").text
|
||||
let ty = p.parseType()
|
||||
result.add(Param(loc: loc, name: name, ptype: ty, isVariadic: true))
|
||||
else:
|
||||
let name = p.expect(tkIdent, "expected parameter name").text
|
||||
let nameTok = p.at
|
||||
var name = ""
|
||||
if nameTok.kind == tkIdent:
|
||||
name = p.advance().text
|
||||
elif nameTok.kind == tkSelf:
|
||||
name = p.advance().text
|
||||
else:
|
||||
name = p.expect(tkIdent, "expected parameter name").text
|
||||
discard p.expect(tkColon, "expected ':' after parameter name")
|
||||
let ty = p.parseType()
|
||||
var defaultVal: Expr = nil
|
||||
@@ -826,6 +841,11 @@ proc parseStructDecl(p: var Parser, isPublic: bool): Decl =
|
||||
discard p.expect(tkLBrace, "expected '{' to start struct body")
|
||||
var fields: seq[StructField] = @[]
|
||||
while not p.check(tkRBrace) and not p.isAtEnd:
|
||||
# Skip newlines
|
||||
while p.check(tkNewLine):
|
||||
discard p.advance()
|
||||
if p.check(tkRBrace) or p.isAtEnd:
|
||||
break
|
||||
let fLoc = p.currentLoc
|
||||
var fPub = false
|
||||
if p.check(tkPub):
|
||||
@@ -853,6 +873,11 @@ proc parseEnumDecl(p: var Parser, isPublic: bool): Decl =
|
||||
discard p.expect(tkLBrace, "expected '{' to start enum body")
|
||||
var variants: seq[EnumVariant] = @[]
|
||||
while not p.check(tkRBrace) and not p.isAtEnd:
|
||||
# Skip newlines
|
||||
while p.check(tkNewLine):
|
||||
discard p.advance()
|
||||
if p.check(tkRBrace) or p.isAtEnd:
|
||||
break
|
||||
let vLoc = p.currentLoc
|
||||
let vName = p.expect(tkIdent, "expected variant name").text
|
||||
var fields: seq[TypeExpr] = @[]
|
||||
@@ -908,6 +933,11 @@ proc parseInterfaceDecl(p: var Parser, isPublic: bool): Decl =
|
||||
discard p.expect(tkLBrace, "expected '{' to start interface body")
|
||||
var methods: seq[Decl] = @[]
|
||||
while not p.check(tkRBrace) and not p.isAtEnd:
|
||||
# Skip newlines
|
||||
while p.check(tkNewLine):
|
||||
discard p.advance()
|
||||
if p.check(tkRBrace) or p.isAtEnd:
|
||||
break
|
||||
methods.add(p.parseFuncDecl(false, false, ParsedAttrs()))
|
||||
discard p.expect(tkRBrace, "expected '}' to close interface")
|
||||
return Decl(kind: dkInterface, loc: loc, isPublic: isPublic,
|
||||
@@ -924,6 +954,11 @@ proc parseImplDecl(p: var Parser): Decl =
|
||||
discard p.expect(tkLBrace, "expected '{' to start impl block")
|
||||
var methods: seq[Decl] = @[]
|
||||
while not p.check(tkRBrace) and not p.isAtEnd:
|
||||
# Skip newlines
|
||||
while p.check(tkNewLine):
|
||||
discard p.advance()
|
||||
if p.check(tkRBrace) or p.isAtEnd:
|
||||
break
|
||||
methods.add(p.parseFuncDecl(false, false, ParsedAttrs()))
|
||||
discard p.expect(tkRBrace, "expected '}' to close impl block")
|
||||
return Decl(kind: dkImpl, loc: loc, declImplTypeName: typeName,
|
||||
|
||||
+90
-1
@@ -14,12 +14,22 @@ type
|
||||
SemaResult* = object
|
||||
diagnostics*: seq[SemaDiagnostic]
|
||||
|
||||
MethodInfo* = object
|
||||
name*: string
|
||||
decl*: Decl
|
||||
params*: seq[Type]
|
||||
retType*: Type
|
||||
|
||||
Sema* = object
|
||||
module*: Module
|
||||
globalScope*: Scope
|
||||
diagnostics*: seq[SemaDiagnostic]
|
||||
# Built-in type mapping from name to Type
|
||||
typeTable*: Table[string, Type]
|
||||
# Type name -> list of methods (from extend blocks)
|
||||
methodTable*: Table[string, seq[MethodInfo]]
|
||||
# Interface name -> interface decl
|
||||
interfaceTable*: Table[string, Decl]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
@@ -94,7 +104,7 @@ proc resolveType(sema: var Sema, te: TypeExpr): Type =
|
||||
# First pass: collect global symbols
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
proc collectGlobals(sema: var Sema) =
|
||||
proc collectGlobals*(sema: var Sema) =
|
||||
for decl in sema.module.items:
|
||||
case decl.kind
|
||||
of dkFunc:
|
||||
@@ -148,6 +158,42 @@ proc collectGlobals(sema: var Sema) =
|
||||
let name = decl.declUsePath[^1]
|
||||
let sym = Symbol(kind: skModule, name: name, typ: makeUnknown(), isPublic: true)
|
||||
discard sema.globalScope.define(sym)
|
||||
of dkInterface:
|
||||
# Register interface for conformance checking
|
||||
sema.interfaceTable[decl.declInterfaceName] = decl
|
||||
let t = makeNamed(decl.declInterfaceName)
|
||||
let sym = Symbol(kind: skType, name: decl.declInterfaceName, typ: t,
|
||||
decl: decl, isPublic: decl.isPublic)
|
||||
if not sema.globalScope.define(sym):
|
||||
sema.emitError(decl.loc, &"duplicate symbol '{decl.declInterfaceName}'")
|
||||
sema.typeTable[decl.declInterfaceName] = t
|
||||
of dkImpl:
|
||||
# Register methods for the type
|
||||
let typeName = decl.declImplTypeName
|
||||
if not sema.methodTable.hasKey(typeName):
|
||||
sema.methodTable[typeName] = @[]
|
||||
for methodDecl in decl.declImplMethods:
|
||||
if methodDecl.kind == dkFunc:
|
||||
var params: seq[Type] = @[]
|
||||
for p in methodDecl.declFuncParams:
|
||||
params.add(sema.resolveType(p.ptype))
|
||||
let retType = if methodDecl.declFuncReturnType != nil:
|
||||
sema.resolveType(methodDecl.declFuncReturnType)
|
||||
else:
|
||||
makeVoid()
|
||||
let info = MethodInfo(
|
||||
name: methodDecl.declFuncName,
|
||||
decl: methodDecl,
|
||||
params: params,
|
||||
retType: retType
|
||||
)
|
||||
sema.methodTable[typeName].add(info)
|
||||
# Also register as a global function: TypeName_MethodName
|
||||
let mangledName = typeName & "_" & methodDecl.declFuncName
|
||||
let sym = Symbol(kind: skFunc, name: mangledName, decl: methodDecl,
|
||||
isPublic: true)
|
||||
sym.typ = makeFunc(params, retType)
|
||||
discard sema.globalScope.define(sym)
|
||||
else:
|
||||
discard
|
||||
|
||||
@@ -278,6 +324,49 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
if expr.exprCallCallee == nil:
|
||||
sema.emitError(expr.loc, "internal error: nil callee in call expression")
|
||||
return makeUnknown()
|
||||
|
||||
# Check for method call: obj.method(args)
|
||||
if expr.exprCallCallee.kind == ekField:
|
||||
let receiver = sema.checkExpr(expr.exprCallCallee.exprFieldObj, scope)
|
||||
let methodName = expr.exprCallCallee.exprFieldName
|
||||
var argTypes = sema.checkExprList(expr.exprCallArgs, scope)
|
||||
|
||||
# Try to find method for receiver type
|
||||
var typeName = ""
|
||||
if receiver.kind == tkNamed:
|
||||
typeName = receiver.name
|
||||
elif receiver.isPointer and receiver.inner.len > 0 and receiver.inner[0].kind == tkNamed:
|
||||
typeName = receiver.inner[0].name
|
||||
|
||||
if typeName != "" and sema.methodTable.hasKey(typeName):
|
||||
for minfo in sema.methodTable[typeName]:
|
||||
if minfo.name == methodName:
|
||||
# Found method - check arguments (skip self parameter)
|
||||
let expectedParams = minfo.params
|
||||
if argTypes.len + 1 < expectedParams.len:
|
||||
sema.emitError(expr.loc, &"too few arguments for method '{methodName}'")
|
||||
elif argTypes.len > expectedParams.len:
|
||||
sema.emitError(expr.loc, &"too many arguments for method '{methodName}'")
|
||||
else:
|
||||
for i in 0 ..< argTypes.len:
|
||||
let paramIdx = i + 1 # skip self
|
||||
if paramIdx < expectedParams.len:
|
||||
if not argTypes[i].isAssignableTo(expectedParams[paramIdx]):
|
||||
sema.emitError(expr.loc, &"argument {i+1}: expected {expectedParams[paramIdx].toString}, got {argTypes[i].toString}")
|
||||
return minfo.retType
|
||||
|
||||
# Not a method - treat as function pointer field
|
||||
let fieldType = sema.checkExpr(expr.exprCallCallee, scope)
|
||||
if fieldType.kind == tkFunc:
|
||||
let expectedParams = fieldType.inner[0..^2]
|
||||
if argTypes.len != expectedParams.len:
|
||||
sema.emitError(expr.loc, &"expected {expectedParams.len} arguments, got {argTypes.len}")
|
||||
return fieldType.inner[^1]
|
||||
else:
|
||||
sema.emitError(expr.loc, &"cannot call non-function field '{methodName}' on type {receiver.toString}")
|
||||
return makeUnknown()
|
||||
|
||||
# Regular function call
|
||||
let calleeType = sema.checkExpr(expr.exprCallCallee, scope)
|
||||
var argTypes = sema.checkExprList(expr.exprCallArgs, scope)
|
||||
if calleeType.kind == tkFunc:
|
||||
|
||||
Reference in New Issue
Block a user