Fix generic struct monomorphization and C output for imports
- Fix nested Lcx_GenerateStructInstance overwriting parent slot by incrementing structCount BEFORE field processing - Fix Lcx_LowerModule to skip generic structs from hm.structs (was missing continue due to bootstrap compiler codegen limitation) - Fix C backend to forward-declare ALL structs at top, removing second generic-only forward declaration section that came too late - Add empty-name guard in CBE_EmitStructDef - Import system: parser supports ::* glob imports, sema two-pass resolution
This commit is contained in:
+5
-20
@@ -747,6 +747,7 @@ func CBE_StructHasValueStructField(st: *HirStruct) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CBE_EmitStructDef(cbe: *CEmitter, st: *HirStruct) {
|
func CBE_EmitStructDef(cbe: *CEmitter, st: *HirStruct) {
|
||||||
|
if String_Eq(st.name, "") { return; }
|
||||||
StringBuilder_Append(&cbe.sb, "struct ");
|
StringBuilder_Append(&cbe.sb, "struct ");
|
||||||
StringBuilder_Append(&cbe.sb, st.name);
|
StringBuilder_Append(&cbe.sb, st.name);
|
||||||
StringBuilder_Append(&cbe.sb, " {\n");
|
StringBuilder_Append(&cbe.sb, " {\n");
|
||||||
@@ -848,10 +849,10 @@ func CBackend_Generate(mod: *HirModule) -> String {
|
|||||||
StringBuilder_Append(&cbe.sb, "void* bux_alloc(unsigned int size);\n");
|
StringBuilder_Append(&cbe.sb, "void* bux_alloc(unsigned int size);\n");
|
||||||
StringBuilder_Append(&cbe.sb, "void bux_free(void* ptr);\n\n");
|
StringBuilder_Append(&cbe.sb, "void bux_free(void* ptr);\n\n");
|
||||||
|
|
||||||
// Forward declare all struct types (skip generics)
|
// Forward declare all struct types (skip empty names)
|
||||||
var si: int = 0;
|
var si: int = 0;
|
||||||
while si < mod.structCount {
|
while si < mod.structCount {
|
||||||
if !CBE_StructHasGeneric(&mod.structs[si]) {
|
if !String_Eq(mod.structs[si].name, "") {
|
||||||
StringBuilder_Append(&cbe.sb, "typedef struct ");
|
StringBuilder_Append(&cbe.sb, "typedef struct ");
|
||||||
StringBuilder_Append(&cbe.sb, mod.structs[si].name);
|
StringBuilder_Append(&cbe.sb, mod.structs[si].name);
|
||||||
StringBuilder_Append(&cbe.sb, " ");
|
StringBuilder_Append(&cbe.sb, " ");
|
||||||
@@ -972,7 +973,7 @@ func CBackend_Generate(mod: *HirModule) -> String {
|
|||||||
// Pass 1: emit structs with no value-typed struct fields (leaf structs)
|
// Pass 1: emit structs with no value-typed struct fields (leaf structs)
|
||||||
si = 0;
|
si = 0;
|
||||||
while si < mod.structCount {
|
while si < mod.structCount {
|
||||||
if CBE_StructHasGeneric(&mod.structs[si]) {
|
if String_Eq(mod.structs[si].name, "") || CBE_StructHasGeneric(&mod.structs[si]) {
|
||||||
si = si + 1;
|
si = si + 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -986,7 +987,7 @@ func CBackend_Generate(mod: *HirModule) -> String {
|
|||||||
// Pass 2: emit structs that contain value-typed struct fields
|
// Pass 2: emit structs that contain value-typed struct fields
|
||||||
si = 0;
|
si = 0;
|
||||||
while si < mod.structCount {
|
while si < mod.structCount {
|
||||||
if CBE_StructHasGeneric(&mod.structs[si]) {
|
if String_Eq(mod.structs[si].name, "") || CBE_StructHasGeneric(&mod.structs[si]) {
|
||||||
si = si + 1;
|
si = si + 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -998,22 +999,6 @@ func CBackend_Generate(mod: *HirModule) -> String {
|
|||||||
si = si + 1;
|
si = si + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Opaque forward declarations for generic structs (used only by pointer)
|
|
||||||
si = 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;
|
|
||||||
}
|
|
||||||
if mod.structCount > 0 {
|
|
||||||
StringBuilder_Append(&cbe.sb, "\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward declarations for all functions (skip generics)
|
// Forward declarations for all functions (skip generics)
|
||||||
var i: int = 0;
|
var i: int = 0;
|
||||||
while i < mod.funcCount {
|
while i < mod.funcCount {
|
||||||
|
|||||||
+32
-28
@@ -245,6 +245,7 @@ func Lcx_MangleName(base: String, typeArg0: String, typeArg1: String, typeArgCou
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Lcx_GenerateStructInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
|
func Lcx_GenerateStructInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
|
||||||
|
if String_Eq(genDecl.strValue, "") { return ""; }
|
||||||
let mangled: String = Lcx_MangleName(genDecl.strValue, typeArg0, typeArg1, typeArgCount);
|
let mangled: String = Lcx_MangleName(genDecl.strValue, typeArg0, typeArg1, typeArgCount);
|
||||||
|
|
||||||
// Check if already generated (linear search in hm.structs)
|
// Check if already generated (linear search in hm.structs)
|
||||||
@@ -268,7 +269,10 @@ func Lcx_GenerateStructInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String
|
|||||||
ctx.substArg1 = typeArg1;
|
ctx.substArg1 = typeArg1;
|
||||||
|
|
||||||
// Generate concrete HirStruct with substituted field types
|
// Generate concrete HirStruct with substituted field types
|
||||||
|
// Reserve the slot BEFORE processing fields so nested generic instantiations
|
||||||
|
// get their own distinct indices and cannot overwrite our slot.
|
||||||
let si: int = ctx.hm.structCount;
|
let si: int = ctx.hm.structCount;
|
||||||
|
ctx.hm.structCount = ctx.hm.structCount + 1;
|
||||||
ctx.hm.structs[si].name = mangled;
|
ctx.hm.structs[si].name = mangled;
|
||||||
ctx.hm.structs[si].fieldCount = genDecl.fieldCount;
|
ctx.hm.structs[si].fieldCount = genDecl.fieldCount;
|
||||||
ctx.hm.structs[si].fields = bux_alloc(genDecl.fieldCount as uint * sizeof(HirStructField)) as *HirStructField;
|
ctx.hm.structs[si].fields = bux_alloc(genDecl.fieldCount as uint * sizeof(HirStructField)) as *HirStructField;
|
||||||
@@ -289,7 +293,6 @@ func Lcx_GenerateStructInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String
|
|||||||
}
|
}
|
||||||
fi = fi + 1;
|
fi = fi + 1;
|
||||||
}
|
}
|
||||||
ctx.hm.structCount = ctx.hm.structCount + 1;
|
|
||||||
|
|
||||||
// Restore old substitution
|
// Restore old substitution
|
||||||
ctx.substParam0 = oldParam0;
|
ctx.substParam0 = oldParam0;
|
||||||
@@ -1651,35 +1654,11 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
|
|||||||
ctx.genFuncs[ctx.genFuncCount] = *decl;
|
ctx.genFuncs[ctx.genFuncCount] = *decl;
|
||||||
ctx.genFuncCount = ctx.genFuncCount + 1;
|
ctx.genFuncCount = ctx.genFuncCount + 1;
|
||||||
}
|
}
|
||||||
if decl.kind == dkStruct && decl.typeParamCount > 0 {
|
if decl.kind == dkStruct {
|
||||||
|
if decl.typeParamCount > 0 {
|
||||||
ctx.genStructs[ctx.genStructCount] = *decl;
|
ctx.genStructs[ctx.genStructCount] = *decl;
|
||||||
ctx.genStructCount = ctx.genStructCount + 1;
|
ctx.genStructCount = ctx.genStructCount + 1;
|
||||||
}
|
} else if !String_Eq(decl.strValue, "") {
|
||||||
if decl.kind == dkFunc && decl.refBody != null as *Block {
|
|
||||||
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 && implDecl.refBody != null as *Block {
|
|
||||||
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
|
// Collect struct definition for C codegen
|
||||||
let si: int = hm.structCount;
|
let si: int = hm.structCount;
|
||||||
hm.structs[si].name = decl.strValue;
|
hm.structs[si].name = decl.strValue;
|
||||||
@@ -1711,6 +1690,31 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
|
|||||||
}
|
}
|
||||||
hm.structCount = hm.structCount + 1;
|
hm.structCount = hm.structCount + 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if decl.kind == dkFunc && decl.refBody != null as *Block {
|
||||||
|
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 && implDecl.refBody != null as *Block {
|
||||||
|
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 == dkConst && hm.constCount < 512 {
|
if decl.kind == dkConst && hm.constCount < 512 {
|
||||||
let ci: int = hm.constCount;
|
let ci: int = hm.constCount;
|
||||||
hm.consts[ci].name = decl.strValue;
|
hm.consts[ci].name = decl.strValue;
|
||||||
|
|||||||
+9
-2
@@ -1434,8 +1434,9 @@ func parserParseImportDecl(p: *Parser, isPublic: bool) -> *Decl {
|
|||||||
}
|
}
|
||||||
d.usePath = pathStr;
|
d.usePath = pathStr;
|
||||||
|
|
||||||
// Optional ::{name1, name2}
|
// Optional ::{name1, name2} or ::*
|
||||||
if parserMatch(p, tkColonColon) && parserCheck(p, tkLBrace) {
|
if parserMatch(p, tkColonColon) {
|
||||||
|
if parserCheck(p, tkLBrace) {
|
||||||
discard parserAdvance(p); // {
|
discard parserAdvance(p); // {
|
||||||
var names: String = "";
|
var names: String = "";
|
||||||
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
||||||
@@ -1453,6 +1454,12 @@ func parserParseImportDecl(p: *Parser, isPublic: bool) -> *Decl {
|
|||||||
discard parserExpect(p, tkRBrace, "expected '}'");
|
discard parserExpect(p, tkRBrace, "expected '}'");
|
||||||
d.useNames = names;
|
d.useNames = names;
|
||||||
d.useKind = 2; // ukMulti
|
d.useKind = 2; // ukMulti
|
||||||
|
} else if parserCheck(p, tkStar) {
|
||||||
|
discard parserAdvance(p); // *
|
||||||
|
d.useKind = 1; // ukGlob
|
||||||
|
} else {
|
||||||
|
d.useKind = 0; // ukSingle
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
d.useKind = 0; // ukSingle
|
d.useKind = 0; // ukSingle
|
||||||
}
|
}
|
||||||
|
|||||||
+85
-92
@@ -997,98 +997,6 @@ func Sema_CollectGlobals(sema: *Sema) {
|
|||||||
discard Scope_Define(sema.scope, sym);
|
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.refType = null as *TypeExpr;
|
|
||||||
sym.isMutable = false;
|
|
||||||
sym.isPublic = true;
|
|
||||||
sym.decl = null as *Decl;
|
|
||||||
let existing: Symbol = Scope_LookupLocal(sema.scope, name);
|
|
||||||
if !String_Eq(existing.name, name) {
|
|
||||||
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.refType = null as *TypeExpr;
|
|
||||||
sym.isMutable = false;
|
|
||||||
sym.isPublic = true;
|
|
||||||
sym.decl = null as *Decl;
|
|
||||||
let existing: Symbol = Scope_LookupLocal(sema.scope, lastSeg);
|
|
||||||
if !String_Eq(existing.name, lastSeg) {
|
|
||||||
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
|
// Extern function
|
||||||
if dk == dkExternFunc {
|
if dk == dkExternFunc {
|
||||||
var sym: Symbol;
|
var sym: Symbol;
|
||||||
@@ -1103,6 +1011,91 @@ func Sema_CollectGlobals(sema: *Sema) {
|
|||||||
|
|
||||||
decl = decl.childDecl2;
|
decl = decl.childDecl2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pass 2: resolve imports by looking up actual symbols
|
||||||
|
decl = sema.module.firstItem;
|
||||||
|
while decl != null as *Decl {
|
||||||
|
let dk: int = decl.kind;
|
||||||
|
|
||||||
|
if dk == dkUse {
|
||||||
|
if decl.useKind == 1 {
|
||||||
|
// Glob import: add all public symbols from scope
|
||||||
|
var scope: *Scope = sema.scope;
|
||||||
|
while scope != null as *Scope {
|
||||||
|
var i: int = 0;
|
||||||
|
while i < scope.count {
|
||||||
|
let sym: Symbol = scope.symbols[i];
|
||||||
|
if sym.isPublic {
|
||||||
|
let existing: Symbol = Scope_LookupLocal(sema.scope, sym.name);
|
||||||
|
if String_Eq(existing.name, "") {
|
||||||
|
discard Scope_Define(sema.scope, sym);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
scope = scope.parent;
|
||||||
|
}
|
||||||
|
} else if decl.useKind == 2 {
|
||||||
|
// Multi-import: resolve each name
|
||||||
|
let namesStr: String = decl.useNames;
|
||||||
|
if !String_Eq(namesStr, "") {
|
||||||
|
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 {
|
||||||
|
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);
|
||||||
|
let found: Symbol = Scope_Lookup(sema.scope, name);
|
||||||
|
if !String_Eq(found.name, "") && found.isPublic {
|
||||||
|
let existing: Symbol = Scope_LookupLocal(sema.scope, name);
|
||||||
|
if String_Eq(existing.name, "") {
|
||||||
|
discard Scope_Define(sema.scope, found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start = pos + 1;
|
||||||
|
}
|
||||||
|
pos = pos + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Single import: resolve last path segment
|
||||||
|
let path: String = decl.usePath;
|
||||||
|
if !String_Eq(path, "") {
|
||||||
|
var lastSeg: String = path;
|
||||||
|
let containsColons: int = bux_str_contains(path, "::");
|
||||||
|
if containsColons != 0 {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let found: Symbol = Scope_Lookup(sema.scope, lastSeg);
|
||||||
|
if !String_Eq(found.name, "") && found.isPublic {
|
||||||
|
let existing: Symbol = Scope_LookupLocal(sema.scope, lastSeg);
|
||||||
|
if String_Eq(existing.name, "") {
|
||||||
|
discard Scope_Define(sema.scope, found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decl = decl.childDecl2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user