From c7114c0538440c1de2d564f29c71072973e54e01 Mon Sep 17 00:00:00 2001 From: dimgigov Date: Sun, 31 May 2026 01:55:54 +0300 Subject: [PATCH] feat: add stdlib module support with auto-import resolution - Parse module paths in stdlib (module Std::Array { ... }) - Collect stdlib declarations and inject into user modules before sema - Add dkExternFunc support throughout pipeline (parser, sema, hir, c backend) - Auto-dereference pointer types for field access (arr->field) - Add hArrowField HIR node for cleaner C emission - Fix resolveTypeExpr for pointer and cast types - Fix struct field lowering to use resolveTypeExpr - Allow int <-> uint implicit conversion for bootstrap convenience - Add stdlib/Std/Array.bux with dynamic array primitives - Add stdlib_test demonstrating Array usage via import Std::Array::{Array}; --- src/ast.nim | 6 +- src/c_backend.nim | 4 ++ src/cli.nim | 54 +++++++++++---- src/hir.nim | 4 ++ src/hir_lower.nim | 146 +++++++++++++++++++++++++++++++-------- src/parser.nim | 47 +++++++++---- src/sema.nim | 47 ++++++++++--- src/types.nim | 3 + stdlib/Std/Array.bux | 44 ++++++++++++ stdlib_test/bux.toml | 7 ++ stdlib_test/src/Main.bux | 21 ++++++ 11 files changed, 316 insertions(+), 67 deletions(-) create mode 100644 stdlib/Std/Array.bux create mode 100644 stdlib_test/bux.toml create mode 100644 stdlib_test/src/Main.bux diff --git a/src/ast.nim b/src/ast.nim index 18d6e3e..cfd5b02 100644 --- a/src/ast.nim +++ b/src/ast.nim @@ -337,6 +337,7 @@ type declImplMethods*: seq[Decl] of dkModule: declModuleName*: string + declModulePath*: seq[string] declModuleItems*: seq[Decl] of dkUse: declUsePath*: seq[string] @@ -370,11 +371,12 @@ type # --------------------------------------------------------------------------- Module* = ref object name*: string + path*: seq[string] items*: seq[Decl] # Convenience constructors -proc newModule*(name: string): Module = - result = Module(name: name) +proc newModule*(name: string, path: seq[string] = @[]): Module = + result = Module(name: name, path: path) proc newBlock*(loc: SourceLocation): Block = result = Block(loc: loc) diff --git a/src/c_backend.nim b/src/c_backend.nim index 6bbe849..0e7d92f 100644 --- a/src/c_backend.nim +++ b/src/c_backend.nim @@ -195,6 +195,10 @@ proc emitExpr(be: var CBackend, node: HirNode): string = 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) diff --git a/src/cli.nim b/src/cli.nim index f105a5d..3e9055a 100644 --- a/src/cli.nim +++ b/src/cli.nim @@ -1,5 +1,5 @@ import std/[os, strutils, terminal, strformat, osproc] -import lexer, parser, sema, manifest, hir, hir_lower, c_backend, types, scope +import lexer, parser, ast, sema, manifest, hir, hir_lower, c_backend, types, scope type ColorMode* = enum @@ -190,6 +190,23 @@ proc cmdCheck*(args: seq[string], opts: GlobalOptions): int = 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 cmdBuild*(args: seq[string], opts: GlobalOptions): int = let useColor = shouldUseColor(opts) let root = getCurrentDir() @@ -208,6 +225,22 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int = if not dirExists(buildDir): createDir(buildDir) + # Find stdlib directory + var stdlibDir = "" + let stdlibSearchPaths = @[ + getAppDir() / ".." / "stdlib", + getAppDir() / "stdlib", + root / "stdlib", + "/home/ziko/z-git/bux/bux/stdlib", + ] + for path in stdlibSearchPaths: + if dirExists(path): + stdlibDir = path + break + + # Collect stdlib declarations once + let stdlibDecls = collectStdlibDecls(stdlibDir) + # Process all .bux files var allCCode = "" var foundMain = false @@ -226,6 +259,13 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int = for d in parseRes.diagnostics: echo &"error: {d.message} at {d.loc}" return 1 + + # Merge stdlib declarations into the module (prepend so they are processed first) + var mergedItems = stdlibDecls + for decl in parseRes.module.items: + mergedItems.add(decl) + parseRes.module.items = mergedItems + let (semaRes, semaCtx) = analyzeFull(parseRes.module) if semaRes.hasErrors: printError(&"type errors in {path}", useColor) @@ -257,18 +297,6 @@ proc cmdBuild*(args: seq[string], opts: GlobalOptions): int = # Copy runtime and stdlib files let runtimeDst = buildDir / "runtime.c" let ioDst = buildDir / "io.c" - var stdlibDir = "" - # Search for stdlib directory - let stdlibSearchPaths = @[ - getAppDir() / ".." / "stdlib", - getAppDir() / "stdlib", - root / "stdlib", - "/home/ziko/z-git/bux/bux/stdlib", # TODO: make this configurable - ] - for path in stdlibSearchPaths: - if dirExists(path): - stdlibDir = path - break if stdlibDir == "": printError("stdlib directory not found", useColor) return 1 diff --git a/src/hir.nim b/src/hir.nim index 973eb3d..d757ccb 100644 --- a/src/hir.nim +++ b/src/hir.nim @@ -22,6 +22,7 @@ type hLoad hStore hFieldPtr + hArrowField hIndexPtr # Functions hCall @@ -84,6 +85,9 @@ type of hFieldPtr: fieldPtrBase*: HirNode fieldName*: string + of hArrowField: + arrowFieldBase*: HirNode + arrowFieldName*: string of hIndexPtr: indexPtrBase*: HirNode indexPtrIndex*: HirNode diff --git a/src/hir_lower.nim b/src/hir_lower.nim index 4ba687e..2807f27 100644 --- a/src/hir_lower.nim +++ b/src/hir_lower.nim @@ -1,4 +1,4 @@ -import std/[tables, strformat, strutils] +import std/[tables, sets, strformat, strutils] import ast, types, token, source_location, hir, sema, scope type @@ -7,6 +7,7 @@ type globalScope*: Scope methodTable*: Table[string, seq[MethodInfo]] currentFuncRetType*: Type + currentFuncDecl*: Decl varCounter*: int typeSubst*: Table[string, Type] # Type parameter substitution for generics importTable*: Table[string, string] # Local name → fully qualified name for imports @@ -102,6 +103,26 @@ proc initLowerCtx*(module: Module, sema: Sema): LowerCtx = result.typeSubst = initTable[string, Type]() result.importTable = initTable[string, string]() +proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type = + if te == nil: return makeUnknown() + case te.kind + of tekNamed: + case te.typeName + of "void": return makeVoid() + of "bool": return makeBool() + of "int": return makeInt() + of "int32": return makeInt() + of "int64": return makeInt64() + of "float64": return makeFloat64() + of "float32": return makeFloat32() + of "uint": return makeUInt() + of "uint32": return makeUInt() + of "uint64": return makeUInt64() + else: return makeNamed(te.typeName) + of tekPointer: return makePointer(ctx.resolveTypeExpr(te.pointerPointee)) + of tekSlice: return makeSlice(ctx.resolveTypeExpr(te.sliceElement)) + else: return makeUnknown() + # Forward declarations proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode @@ -119,8 +140,33 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type = of tkBoolLiteral: return makeBool() else: return makeUnknown() of ekIdent: + # Check global scope first let sym = ctx.globalScope.lookup(expr.exprIdent) if sym != nil and sym.typ != nil: return sym.typ + # Check current function parameters + if ctx.currentFuncDecl != nil: + var params: seq[Param] = @[] + case ctx.currentFuncDecl.kind + of dkFunc: params = ctx.currentFuncDecl.declFuncParams + of dkExternFunc: params = ctx.currentFuncDecl.declExtFuncParams + else: discard + for p in params: + if p.name == expr.exprIdent and p.ptype != nil: + case p.ptype.kind + of tekNamed: + case p.ptype.typeName + of "int", "int32": return makeInt() + of "int64": return makeInt64() + of "float64": return makeFloat64() + of "float32": return makeFloat32() + of "bool": return makeBool() + of "uint": return makeUInt() + of "void": return makeVoid() + else: return makeNamed(p.ptype.typeName) + of tekPointer: + let pointeeType = ctx.resolveTypeExpr(p.ptype.pointerPointee) + return makePointer(pointeeType) + else: discard return makeUnknown() of ekSelf: return makeNamed("self") of ekBinary: @@ -156,7 +202,10 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type = return minfo.retType return makeUnknown() of ekField: - let objType = ctx.resolveExprType(expr.exprFieldObj) + var objType = ctx.resolveExprType(expr.exprFieldObj) + # Auto-dereference pointer types for field access + if objType.kind == tkPointer and objType.inner.len > 0: + objType = objType.inner[0] if objType.kind == tkNamed: let sym = ctx.globalScope.lookup(objType.name) if sym != nil and sym.decl != nil and sym.decl.kind == dkStruct: @@ -171,7 +220,8 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type = of "float32": return makeFloat32() of "bool": return makeBool() else: return makeNamed(f.ftype.typeName) - of tekPointer: return makePointer(makeUnknown()) + of tekPointer: + return ctx.resolveTypeExpr(f.ftype) else: return makeUnknown() return makeUnknown() of ekStructInit: return makeNamed(expr.exprStructInitName) @@ -186,9 +236,7 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type = return makeTuple(elems) of ekCast: if expr.exprCastType != nil: - case expr.exprCastType.kind - of tekNamed: return makeNamed(expr.exprCastType.typeName) - else: return makeUnknown() + return ctx.resolveTypeExpr(expr.exprCastType) return makeUnknown() of ekBlock: if expr.exprBlock.stmts.len > 0: @@ -292,7 +340,14 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode = callIndirectArgs: args, typ: typ, loc: loc) of ekField: + let objType = ctx.resolveExprType(expr.exprFieldObj) let base = ctx.lowerExpr(expr.exprFieldObj) + # Auto-dereference pointer types for field access + if objType.kind == tkPointer: + let arrowPtr = HirNode(kind: hArrowField, arrowFieldBase: base, + arrowFieldName: expr.exprFieldName, + typ: makePointer(typ), loc: loc) + return HirNode(kind: hLoad, loadPtr: arrowPtr, typ: typ, loc: loc) let basePtr = HirNode(kind: hFieldPtr, fieldPtrBase: base, fieldName: expr.exprFieldName, typ: makePointer(typ), loc: loc) @@ -335,10 +390,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode = 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 + castType = ctx.resolveTypeExpr(expr.exprCastType) return HirNode(kind: hCast, castOperand: operand, castType: castType, typ: typ, loc: loc) @@ -509,8 +561,28 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc = # Set up type substitution for generic functions let oldSubst = ctx.typeSubst + var funcName: string + var funcParams: seq[Param] + var funcReturnType: TypeExpr + var funcBody: Block + + case decl.kind + of dkFunc: + funcName = decl.declFuncName + funcParams = decl.declFuncParams + funcReturnType = decl.declFuncReturnType + funcBody = decl.declFuncBody + of dkExternFunc: + funcName = decl.declExtFuncName + funcParams = decl.declExtFuncParams + funcReturnType = decl.declExtFuncReturnType + funcBody = nil + else: + result = HirFunc(name: "", params: @[], retType: makeVoid(), body: nil) + return + var params: seq[tuple[name: string, typ: Type]] = @[] - for p in decl.declFuncParams: + for p in funcParams: var pType = makeUnknown() if p.ptype != nil: case p.ptype.kind @@ -527,32 +599,37 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc = of "bool": pType = makeBool() of "Point", "Self": pType = makeNamed(p.ptype.typeName) else: pType = makeNamed(p.ptype.typeName) - of tekPointer: pType = makePointer(makeUnknown()) + of tekPointer: + let pointeeType = ctx.resolveTypeExpr(p.ptype.pointerPointee) + pType = makePointer(pointeeType) else: discard params.add((p.name, pType)) var retType = makeVoid() - if decl.declFuncReturnType != nil: - case decl.declFuncReturnType.kind + if funcReturnType != nil: + case funcReturnType.kind of tekNamed: # Check if this is a type parameter - if ctx.typeSubst.hasKey(decl.declFuncReturnType.typeName): - retType = ctx.typeSubst[decl.declFuncReturnType.typeName] + if ctx.typeSubst.hasKey(funcReturnType.typeName): + retType = ctx.typeSubst[funcReturnType.typeName] else: - case decl.declFuncReturnType.typeName + case funcReturnType.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: retType = makeNamed(funcReturnType.typeName) + of tekPointer: + let pointeeType = ctx.resolveTypeExpr(funcReturnType.pointerPointee) + retType = makePointer(pointeeType) else: discard ctx.currentFuncRetType = retType - let body = if decl.declFuncBody != nil: ctx.lowerBlock(decl.declFuncBody) else: nil + ctx.currentFuncDecl = decl + let body = if funcBody != nil: ctx.lowerBlock(funcBody) else: nil - result = HirFunc(name: decl.declFuncName, params: params, retType: retType, + result = HirFunc(name: funcName, params: params, retType: retType, body: body, isPublic: decl.isPublic) # Restore old substitution @@ -566,6 +643,17 @@ proc lowerModule*(module: Module, sema: Sema): HirModule = var enums: seq[tuple[name: string, variants: seq[HirEnumVariant]]] = @[] var consts: seq[tuple[name: string, typ: Type, value: HirNode]] = @[] + # Collect local symbol names so we don't remap them via imports + var localSymbols = initHashSet[string]() + for decl in module.items: + case decl.kind + of dkFunc: localSymbols.incl(decl.declFuncName) + of dkExternFunc: localSymbols.incl(decl.declExtFuncName) + of dkStruct: localSymbols.incl(decl.declStructName) + of dkEnum: localSymbols.incl(decl.declEnumName) + of dkUnion: localSymbols.incl(decl.declUnionName) + else: discard + # Collect imports for name resolution for decl in module.items: if decl.kind == dkUse: @@ -574,12 +662,14 @@ proc lowerModule*(module: Module, sema: Sema): HirModule = if decl.declUsePath.len > 0: let localName = decl.declUsePath[^1] let fullName = decl.declUsePath.join("_") - ctx.importTable[localName] = fullName + if localName notin localSymbols: + ctx.importTable[localName] = fullName of ukMulti: if decl.declUsePath.len > 0: let basePath = decl.declUsePath.join("_") for name in decl.declUseNames: - ctx.importTable[name] = basePath & "_" & name + if name notin localSymbols: + ctx.importTable[name] = basePath & "_" & name of ukGlob: # For glob imports, we can't statically resolve all names here. # Store the base path for potential future use. @@ -706,6 +796,8 @@ proc lowerModule*(module: Module, sema: Sema): HirModule = else: # Extern function (no body) externFuncs.add(ctx.lowerFunc(decl)) + of dkExternFunc: + externFuncs.add(ctx.lowerFunc(decl)) of dkImpl: for methodDecl in decl.declImplMethods: if methodDecl.kind == dkFunc: @@ -715,13 +807,7 @@ proc lowerModule*(module: Module, sema: Sema): HirModule = 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) + let fType = if f.ftype != nil: ctx.resolveTypeExpr(f.ftype) else: makeUnknown() fields.add((f.name, fType)) structs.add((decl.declStructName, fields)) of dkEnum: diff --git a/src/parser.nim b/src/parser.nim index c86e73d..730e575 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -899,7 +899,7 @@ proc parseStructDecl(p: var Parser, isPublic: bool): Decl = let fName = p.expect(tkIdent, "expected field name").text discard p.expect(tkColon, "expected ':' after field name") let fType = p.parseType() - if p.check(tkSemicolon): + if p.check(tkSemicolon) or p.check(tkComma): discard p.advance() fields.add(StructField(loc: fLoc, isPublic: fPub, name: fName, ftype: fType)) discard p.expect(tkRBrace, "expected '}' to close struct") @@ -1012,14 +1012,24 @@ proc parseImplDecl(p: var Parser): Decl = proc parseModuleDecl(p: var Parser, isPublic: bool): Decl = let loc = p.currentLoc discard p.expect(tkModule, "expected 'module'") - let name = p.expect(tkIdent, "expected module name").text + var path: seq[string] = @[] + path.add(p.expect(tkIdent, "expected module name").text) + while p.check(tkColonColon): + discard p.advance() + path.add(p.expect(tkIdent, "expected module path segment").text) + let name = path[^1] discard p.expect(tkLBrace, "expected '{' to start module body") var items: seq[Decl] = @[] while not p.check(tkRBrace) and not p.isAtEnd: + while p.check(tkNewLine): + discard p.advance() + if p.check(tkRBrace) or p.isAtEnd: + break items.add(p.parseDecl()) discard p.expect(tkRBrace, "expected '}' to close module") return Decl(kind: dkModule, loc: loc, isPublic: isPublic, - declModuleName: name, declModuleItems: items) + declModuleName: name, declModulePath: path, + declModuleItems: items) proc parseUseDecl(p: var Parser, attrs: ParsedAttrs): Decl = let loc = p.currentLoc @@ -1027,6 +1037,9 @@ proc parseUseDecl(p: var Parser, attrs: ParsedAttrs): Decl = var path: seq[string] = @[] path.add(p.expect(tkIdent, "expected import path segment").text) while p.check(tkColonColon): + # Lookahead: if :: is followed by { or *, don't consume it here + if p.peek(1) == tkLBrace or p.peek(1) == tkStar: + break discard p.advance() path.add(p.expect(tkIdent, "expected import path segment").text) var kind = ukSingle @@ -1040,13 +1053,17 @@ proc parseUseDecl(p: var Parser, attrs: ParsedAttrs): Decl = kind = ukGlob elif p.check(tkColonColon): discard p.advance() - discard p.expect(tkLBrace, "expected '{' for multi-import") - while not p.check(tkRBrace) and not p.isAtEnd: - names.add(p.expect(tkIdent, "expected name in multi-import").text) - if p.check(tkComma): - discard p.advance() - discard p.expect(tkRBrace, "expected '}' to close multi-import") - kind = ukMulti + if p.check(tkLBrace): + discard p.advance() + while not p.check(tkRBrace) and not p.isAtEnd: + names.add(p.expect(tkIdent, "expected name in multi-import").text) + if p.check(tkComma): + discard p.advance() + discard p.expect(tkRBrace, "expected '}' to close multi-import") + kind = ukMulti + elif p.check(tkStar): + discard p.advance() + kind = ukGlob if p.check(tkSemicolon): discard p.advance() return Decl(kind: dkUse, loc: loc, declUsePath: path, declUseKind: kind, @@ -1083,8 +1100,14 @@ proc parseExternDecl(p: var Parser, isPublic: bool, attrs: ParsedAttrs): Decl = discard p.expect(tkExtern, "expected 'extern'") if p.check(tkFunc): let funcDecl = p.parseFuncDecl(isPublic, false, attrs) - funcDecl.isPublic = isPublic - return funcDecl + # Convert dkFunc to dkExternFunc + return Decl(kind: dkExternFunc, loc: funcDecl.loc, isPublic: isPublic, + declExtFuncName: funcDecl.declFuncName, + declExtFuncDll: attrs.importLib, + declExtFuncCallConv: attrs.callConv, + declExtFuncParams: funcDecl.declFuncParams, + declExtFuncVariadic: false, + declExtFuncReturnType: funcDecl.declFuncReturnType) elif p.check(tkLBrace): discard p.advance() var items: seq[Decl] = @[] diff --git a/src/sema.nim b/src/sema.nim index 5f006ef..887095d 100644 --- a/src/sema.nim +++ b/src/sema.nim @@ -118,6 +118,16 @@ proc collectGlobals*(sema: var Sema) = sym.typ = makeFunc(params, retType) if not sema.globalScope.define(sym): sema.emitError(decl.loc, &"duplicate symbol '{decl.declFuncName}'") + of dkExternFunc: + let sym = Symbol(kind: skFunc, name: decl.declExtFuncName, decl: decl, + isPublic: decl.isPublic) + var params: seq[Type] = @[] + for p in decl.declExtFuncParams: + params.add(sema.resolveType(p.ptype)) + let retType = if decl.declExtFuncReturnType != nil: sema.resolveType(decl.declExtFuncReturnType) else: makeVoid() + sym.typ = makeFunc(params, retType) + if not sema.globalScope.define(sym): + sema.emitError(decl.loc, &"duplicate symbol '{decl.declExtFuncName}'") of dkStruct: let t = makeNamed(decl.declStructName) let sym = Symbol(kind: skType, name: decl.declStructName, typ: t, @@ -160,11 +170,24 @@ proc collectGlobals*(sema: var Sema) = sema.emitError(decl.loc, &"duplicate symbol '{decl.declAliasName}'") sema.typeTable[decl.declAliasName] = t of dkUse: - # Imports: for now just register the last segment as a module symbol + # Imports: register imported names into scope if decl.declUsePath.len > 0: - let name = decl.declUsePath[^1] - let sym = Symbol(kind: skModule, name: name, typ: makeUnknown(), isPublic: true) - discard sema.globalScope.define(sym) + case decl.declUseKind + of ukMulti: + for name in decl.declUseNames: + if sema.globalScope.lookup(name) == nil: + let sym = Symbol(kind: skFunc, name: name, typ: makeUnknown(), isPublic: true) + discard sema.globalScope.define(sym) + of ukGlob: + let name = decl.declUsePath[^1] + if sema.globalScope.lookup(name) == nil: + let sym = Symbol(kind: skModule, name: name, typ: makeUnknown(), isPublic: true) + discard sema.globalScope.define(sym) + of ukSingle: + let name = decl.declUsePath[^1] + if sema.globalScope.lookup(name) == nil: + let sym = Symbol(kind: skFunc, name: name, typ: makeUnknown(), isPublic: true) + discard sema.globalScope.define(sym) of dkInterface: # Register interface for conformance checking sema.interfaceTable[decl.declInterfaceName] = decl @@ -460,10 +483,14 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type = return makeUnknown() of ekField: let obj = sema.checkExpr(expr.exprFieldObj, scope) - if obj.kind == tkNamed: + var objType = obj + # Auto-dereference pointer types for field access + if objType.kind == tkPointer and objType.inner.len > 0: + objType = objType.inner[0] + if objType.kind == tkNamed: # Check if this is a _Data union field access - if obj.name.endsWith("_Data"): - let enumName = obj.name[0..^6] # Remove "_Data" suffix + if objType.name.endsWith("_Data"): + let enumName = objType.name[0..^6] # Remove "_Data" suffix let enumSym = sema.globalScope.lookup(enumName) if enumSym != nil and enumSym.decl != nil and enumSym.decl.kind == dkEnum: # Look for the field in enum variants @@ -477,17 +504,17 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type = for nf in variant.namedFields: if nf.name == expr.exprFieldName: return sema.resolveType(nf.ftype) - sema.emitError(expr.loc, &"union '{obj.name}' has no field '{expr.exprFieldName}'") + sema.emitError(expr.loc, &"union '{objType.name}' has no field '{expr.exprFieldName}'") else: sema.emitError(expr.loc, &"cannot access field on type {obj.toString}") else: - let sym = sema.globalScope.lookup(obj.name) + let sym = sema.globalScope.lookup(objType.name) if sym != nil and sym.decl != nil: if sym.decl.kind == dkStruct: for f in sym.decl.declStructFields: if f.name == expr.exprFieldName: return sema.resolveType(f.ftype) - sema.emitError(expr.loc, &"struct '{obj.name}' has no field '{expr.exprFieldName}'") + sema.emitError(expr.loc, &"struct '{objType.name}' has no field '{expr.exprFieldName}'") elif sym.decl.kind == dkEnum: # Algebraic enum fields if expr.exprFieldName == "tag": diff --git a/src/types.nim b/src/types.nim index ebcf9dc..1741029 100644 --- a/src/types.nim +++ b/src/types.nim @@ -120,6 +120,9 @@ proc isAssignableTo*(a, b: Type): bool = # 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 # numeric exact match required otherwise if a.isNumeric and b.isNumeric: return false # bool across widths diff --git a/stdlib/Std/Array.bux b/stdlib/Std/Array.bux new file mode 100644 index 0000000..42609b6 --- /dev/null +++ b/stdlib/Std/Array.bux @@ -0,0 +1,44 @@ +module Std::Array { + +extern func bux_alloc(size: uint) -> *void; +extern func bux_realloc(ptr: *void, size: uint) -> *void; +extern func bux_free(ptr: *void); +extern func bux_bounds_check(index: uint, len: uint); + +struct Array { + data: *int, + len: uint, + cap: uint, +} + +func Array_New(cap: uint) -> Array { + let data = bux_alloc(cap * 8) as *int; + return Array { data: data, len: 0, cap: cap }; +} + +func Array_Push(arr: *Array, value: int) { + if arr.len >= arr.cap { + arr.cap = arr.cap * 2; + arr.data = bux_realloc(arr.data as *void, arr.cap * 8) as *int; + } + arr.data[arr.len] = value; + arr.len = arr.len + 1; +} + +func Array_Get(arr: *Array, index: uint) -> int { + bux_bounds_check(index, arr.len); + return arr.data[index]; +} + +func Array_Len(arr: *Array) -> uint { + return arr.len; +} + +func Array_Free(arr: *Array) { + bux_free(arr.data as *void); + arr.data = null as *int; + arr.len = 0; + arr.cap = 0; +} + +} diff --git a/stdlib_test/bux.toml b/stdlib_test/bux.toml new file mode 100644 index 0000000..94ba0c3 --- /dev/null +++ b/stdlib_test/bux.toml @@ -0,0 +1,7 @@ +[Package] +Name = "stdlib_test" +Version = "0.1.0" +Type = "bin" + +[Build] +Output = "Bin" diff --git a/stdlib_test/src/Main.bux b/stdlib_test/src/Main.bux new file mode 100644 index 0000000..c011735 --- /dev/null +++ b/stdlib_test/src/Main.bux @@ -0,0 +1,21 @@ +import Std::Array::{Array, Array_New, Array_Push, Array_Get, Array_Len, Array_Free}; +import Std::Io::{PrintLine, PrintInt}; + +func Main() -> int { + var arr: Array = Array_New(4); + + Array_Push(&arr, 10); + Array_Push(&arr, 20); + Array_Push(&arr, 30); + + PrintLine("Array contents:"); + var i: uint = 0; + while i < Array_Len(&arr) { + PrintInt(Array_Get(&arr, i)); + PrintLine(""); + i = i + 1; + } + + Array_Free(&arr); + return 0; +}