mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
Support for "static" keyword.
This commit is contained in:
committed by
Christoffer Lerno
parent
0605a8c500
commit
dd8b850544
@@ -21,7 +21,7 @@ color brightred "@\<[A-Za-z_]+\>"
|
||||
color green "\<(virtual|void|bool|quad|double|float|long|ulong|int|uint|short|ushort|ichar|char|isize|usize|iptr|uptr|iptrdiff|uptrdiff|half)\>"
|
||||
|
||||
# Keywords
|
||||
color yellow "\<(alias|as|asm|assert|attribute|break|case|catch|const|continue|default|defer|define|do|else|enum|extern|error|false|for|foreach|func|generic|if|import|interface|macro|module|nextcase|null|public|return|static|struct|switch|template|true|try|typeid|typeof|union|while|var|volatile)\>"
|
||||
color yellow "\<(alias|as|asm|assert|attribute|break|case|catch|const|continue|default|defer|define|do|else|enum|extern|error|false|for|foreach|func|generic|if|import|interface|macro|module|nextcase|null|private|return|static|struct|switch|template|true|try|typeid|typeof|union|while|var|volatile)\>"
|
||||
|
||||
# Strings
|
||||
color brightblack "\"[^"]*\""
|
||||
|
||||
@@ -359,7 +359,7 @@ bool lexScanNumber(LexInfo *lex) {
|
||||
}
|
||||
|
||||
/** List of all reserved names (excluding literals) */
|
||||
static VmLiterals ReservedNames[] = {
|
||||
private VmLiterals ReservedNames[] = {
|
||||
SymAnd,
|
||||
SymAsync,
|
||||
SymBaseurl,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module std::array::linkedlist<Type>;
|
||||
import std::mem;
|
||||
|
||||
static struct Node
|
||||
private struct Node
|
||||
{
|
||||
Node *next;
|
||||
Node *prev;
|
||||
@@ -20,7 +20,7 @@ func void LinkedList.push(LinkedList *list, Type value)
|
||||
list.linkLast(value);
|
||||
}
|
||||
|
||||
static func void LinkedList.linkFirst(LinkedList *list, Type value)
|
||||
private func void LinkedList.linkFirst(LinkedList *list, Type value)
|
||||
{
|
||||
Node *first = list.first;
|
||||
Node *new_node = @mem::malloc(Node);
|
||||
@@ -37,7 +37,7 @@ static func void LinkedList.linkFirst(LinkedList *list, Type value)
|
||||
list.size++;
|
||||
}
|
||||
|
||||
static func void LinkedList.linkLast(LinkedList *list, Type value)
|
||||
private func void LinkedList.linkLast(LinkedList *list, Type value)
|
||||
{
|
||||
Node *last = list.last;
|
||||
Node *new_node = mem::alloc(Node.sizeof);
|
||||
@@ -84,7 +84,7 @@ func Type LinkedList.get(LinkedList* list, usize index)
|
||||
/**
|
||||
* @require succ != null
|
||||
**/
|
||||
static func void LinkedList.linkBefore(LinkedList *list, Node *succ, Type value)
|
||||
private func void LinkedList.linkBefore(LinkedList *list, Node *succ, Type value)
|
||||
{
|
||||
Node* pred = succ.prev;
|
||||
Node* new_node = @mem::malloc(Node);
|
||||
@@ -104,7 +104,7 @@ static func void LinkedList.linkBefore(LinkedList *list, Node *succ, Type value)
|
||||
/**
|
||||
* @require f == list.first && f != null
|
||||
**/
|
||||
static func void unlinkFirst(LinkedList* list, Node* f)
|
||||
private func void unlinkFirst(LinkedList* list, Node* f)
|
||||
{
|
||||
Node* next = f.next;
|
||||
mem::free(f);
|
||||
@@ -123,7 +123,7 @@ static func void unlinkFirst(LinkedList* list, Node* f)
|
||||
/**
|
||||
* @require l == list.last && l != null
|
||||
**/
|
||||
static func void LinkedList.unlinkLast(LinkedList *list, Node* l)
|
||||
private func void LinkedList.unlinkLast(LinkedList *list, Node* l)
|
||||
{
|
||||
Node* prev = l.prev;
|
||||
list.last = prev;
|
||||
@@ -142,7 +142,7 @@ static func void LinkedList.unlinkLast(LinkedList *list, Node* l)
|
||||
/**
|
||||
* @require x != null
|
||||
**/
|
||||
static func void LinkedList.unlink(LinkedList* list, Node* x)
|
||||
private func void LinkedList.unlink(LinkedList* list, Node* x)
|
||||
{
|
||||
Node* next = x.next;
|
||||
Node* prev = x.prev;
|
||||
|
||||
@@ -8,7 +8,7 @@ struct List
|
||||
Type *entries;
|
||||
}
|
||||
|
||||
static func void List.ensureCapacity(List *list) @inline
|
||||
private func void List.ensureCapacity(List *list) @inline
|
||||
{
|
||||
if (list.capacity == list.size)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module std::math;
|
||||
|
||||
static union DoubleLong
|
||||
private union DoubleLong
|
||||
{
|
||||
double f;
|
||||
ulong i;
|
||||
@@ -108,7 +108,7 @@ func double cos_limited(double x, double y)
|
||||
return w + (((1.0 - w) - hz) + (z*r - x*y));
|
||||
}
|
||||
|
||||
static func double sin_limited(double x, double y, bool iy)
|
||||
private func double sin_limited(double x, double y, bool iy)
|
||||
{
|
||||
const double S1 = -1.66666666666666324348e-01; // 0xBFC55555, 0x55555549
|
||||
const double S2 = 8.33333333332248946124e-03; // 0x3F811111, 0x1110F8A6
|
||||
|
||||
@@ -200,7 +200,7 @@ static void register_generic_decls(Module *module, Decl **decls)
|
||||
}
|
||||
static void analyze_generic_module(Module *module)
|
||||
{
|
||||
assert(module->parameters);
|
||||
assert(module->parameters && module->is_generic);
|
||||
// TODO maybe do this analysis: sema_analysis_pass_process_imports(module);
|
||||
VECEACH(module->contexts, index)
|
||||
{
|
||||
@@ -508,6 +508,7 @@ Module *compiler_find_or_create_module(Path *module_name, TokenId *parameters, b
|
||||
module->name = module_name;
|
||||
module->stage = ANALYSIS_NOT_BEGUN;
|
||||
module->parameters = parameters;
|
||||
module->is_generic = vec_size(parameters) > 0;
|
||||
module->is_private = is_private;
|
||||
stable_init(&module->symbols, 0x10000);
|
||||
stable_set(&global_context.modules, module_name->module, module);
|
||||
|
||||
@@ -340,9 +340,9 @@ void llvm_emit_ptr_from_array(GenContext *c, BEValue *value)
|
||||
UNREACHABLE
|
||||
}
|
||||
}
|
||||
static void gencontext_emit_global_variable_init(GenContext *c, Decl *decl)
|
||||
void llvm_emit_global_variable_init(GenContext *c, Decl *decl)
|
||||
{
|
||||
assert(decl->var.kind == VARDECL_GLOBAL || decl->var.kind == VARDECL_CONST);
|
||||
assert(decl->var.kind == VARDECL_GLOBAL || decl->var.kind == VARDECL_CONST || decl->var.is_static);
|
||||
|
||||
// Skip real constants.
|
||||
if (!decl->type) return;
|
||||
@@ -475,6 +475,8 @@ void llvm_emit_and_set_decl_alloca(GenContext *c, Decl *decl)
|
||||
|
||||
void llvm_emit_local_var_alloca(GenContext *c, Decl *decl)
|
||||
{
|
||||
assert(!decl->var.is_static);
|
||||
printf("My name %s\n", decl->name);
|
||||
llvm_emit_and_set_decl_alloca(c, decl);
|
||||
if (llvm_use_debug(c))
|
||||
{
|
||||
@@ -981,7 +983,7 @@ void *llvm_gen(Module *module)
|
||||
}
|
||||
VECEACH(context->vars, i)
|
||||
{
|
||||
gencontext_emit_global_variable_init(gen_context, context->vars[i]);
|
||||
llvm_emit_global_variable_init(gen_context, context->vars[i]);
|
||||
}
|
||||
VECEACH(context->functions, i)
|
||||
{
|
||||
|
||||
@@ -232,13 +232,14 @@ void llvm_emit_defer(GenContext *c, AstId defer_start, AstId defer_end);
|
||||
void llvm_emit_extern_decl(GenContext *context, Decl *decl);
|
||||
|
||||
LLVMValueRef llvm_emit_const_aggregate(GenContext *c, Expr *expr, bool *modified);
|
||||
void llvm_emit_expr(GenContext *c, BEValue *value, Expr *expr);
|
||||
void llvm_emit_global_variable_init(GenContext *c, Decl *decl);
|
||||
LLVMValueRef llvm_emit_int_comparison(GenContext *c, Type *lhs_type, Type *rhs_type, LLVMValueRef lhs_value, LLVMValueRef rhs_value, BinaryOp binary_op);
|
||||
|
||||
LLVMValueRef llvm_emit_is_no_error_value(GenContext *c, BEValue *value);
|
||||
void llvm_emit_len_for_expr(GenContext *c, BEValue *be_value, BEValue *expr_to_len);
|
||||
LLVMValueRef llvm_emit_load_aligned(GenContext *c, LLVMTypeRef type, LLVMValueRef pointer, AlignSize alignment, const char *name);
|
||||
void llvm_emit_local_var_alloca(GenContext *c, Decl *decl);
|
||||
void llvm_emit_expr(GenContext *c, BEValue *value, Expr *expr);
|
||||
LLVMValueRef llvm_emit_memclear_size_align(GenContext *c, LLVMValueRef ref, uint64_t size, unsigned align, bool bitcast);
|
||||
LLVMValueRef llvm_emit_memclear(GenContext *c, BEValue *ref);
|
||||
void llvm_emit_memcpy(GenContext *c, LLVMValueRef dest, unsigned dest_align, LLVMValueRef source, unsigned src_align, uint64_t len);
|
||||
|
||||
@@ -44,12 +44,25 @@ static LLVMValueRef llvm_emit_decl(GenContext *c, Ast *ast)
|
||||
Decl *decl = ast->declare_stmt;
|
||||
|
||||
LLVMTypeRef alloc_type = llvm_get_type(c, type_lowering(decl->type));
|
||||
|
||||
if (decl->var.is_static)
|
||||
{
|
||||
decl->backend_ref = LLVMAddGlobal(c->module, llvm_get_type(c, decl->type), "tempglobal");
|
||||
llvm_emit_global_variable_init(c, decl);
|
||||
if (decl->var.failable)
|
||||
{
|
||||
decl->var.failable_ref = LLVMAddGlobal(c->module, llvm_get_type(c, type_error), decl->name);
|
||||
LLVMBuildStore(c->builder, LLVMConstNull(llvm_get_type(c, type_error)), decl->var.failable_ref);
|
||||
}
|
||||
return decl->backend_ref;
|
||||
}
|
||||
llvm_emit_local_var_alloca(c, decl);
|
||||
if (decl->var.failable)
|
||||
{
|
||||
decl->var.failable_ref = llvm_emit_alloca_aligned(c, type_error, decl->name);
|
||||
LLVMBuildStore(c->builder, LLVMConstNull(llvm_get_type(c, type_error)), decl->var.failable_ref);
|
||||
}
|
||||
|
||||
Expr *init = decl->var.init_expr;
|
||||
if (init)
|
||||
{
|
||||
|
||||
@@ -71,7 +71,7 @@ void recover_top_level(Context *context)
|
||||
{
|
||||
switch (context->tok.type)
|
||||
{
|
||||
case TOKEN_STATIC:
|
||||
case TOKEN_PRIVATE:
|
||||
case TOKEN_STRUCT:
|
||||
case TOKEN_INTERFACE:
|
||||
case TOKEN_IMPORT:
|
||||
@@ -800,6 +800,8 @@ Decl *parse_decl(Context *context)
|
||||
return parse_const_declaration(context, VISIBLE_LOCAL);
|
||||
}
|
||||
|
||||
bool is_static = try_consume(context, TOKEN_STATIC);
|
||||
|
||||
TypeInfo *type = TRY_TYPE_OR(parse_type(context), poisoned_decl);
|
||||
|
||||
bool failable = try_consume(context, TOKEN_BANG);
|
||||
@@ -811,7 +813,11 @@ Decl *parse_decl(Context *context)
|
||||
return poisoned_decl;
|
||||
}
|
||||
decl->var.failable = failable;
|
||||
|
||||
decl->var.is_static = is_static;
|
||||
if (is_static)
|
||||
{
|
||||
printf("Added static to %s %d\n", decl->name, decl->var.is_static);
|
||||
}
|
||||
return decl;
|
||||
}
|
||||
|
||||
@@ -2239,7 +2245,7 @@ Decl *parse_top_level_statement(Context *context)
|
||||
Visibility visibility = VISIBLE_PUBLIC;
|
||||
switch (context->tok.type)
|
||||
{
|
||||
case TOKEN_STATIC:
|
||||
case TOKEN_PRIVATE:
|
||||
visibility = VISIBLE_MODULE;
|
||||
advance(context);
|
||||
break;
|
||||
|
||||
@@ -19,7 +19,6 @@ static inline Ast *parse_declaration_stmt(Context *context)
|
||||
{
|
||||
Ast *decl_stmt = AST_NEW_TOKEN(AST_DECLARE_STMT, context->tok);
|
||||
decl_stmt->declare_stmt = TRY_DECL_OR(parse_decl(context), poisoned_ast);
|
||||
decl_stmt->declare_stmt->var.is_static = try_consume(context, TOKEN_STATIC);
|
||||
CONSUME_OR(TOKEN_EOS, poisoned_ast);
|
||||
return decl_stmt;
|
||||
}
|
||||
|
||||
@@ -700,7 +700,7 @@ static AttributeType sema_analyse_attribute(Context *context, Attr *attr, Attrib
|
||||
return type;
|
||||
case ATTRIBUTE_SECTION:
|
||||
case ATTRIBUTE_CNAME:
|
||||
if (context->module->parameters)
|
||||
if (context->module->is_generic)
|
||||
{
|
||||
SEMA_TOKID_ERROR(attr->name, "'cname' attributes are not allowed in generic modules.");
|
||||
return false;
|
||||
|
||||
@@ -67,7 +67,7 @@ static Decl *sema_resolve_path_symbol(Context *context, const char *symbol, Path
|
||||
{
|
||||
Decl *import = context->imports[i];
|
||||
|
||||
if (import->module->parameters) continue;
|
||||
if (import->module->is_generic) continue;
|
||||
|
||||
// 4. Can we match a subpath?
|
||||
if (path->len > import->import.path->len) continue;
|
||||
@@ -140,7 +140,7 @@ static Decl *sema_resolve_no_path_symbol(Context *context, const char *symbol,
|
||||
if (!decl_ok(import)) continue;
|
||||
|
||||
// Skip parameterized modules
|
||||
if (import->module->parameters) continue;
|
||||
if (import->module->is_generic) continue;
|
||||
|
||||
Decl *found = module_find_symbol(import->module, symbol);
|
||||
if (!found) continue;
|
||||
@@ -242,7 +242,6 @@ Decl *sema_resolve_parameterized_symbol(Context *context, TokenId symbol, Path *
|
||||
{
|
||||
Decl *ambiguous_other_decl = NULL;
|
||||
Decl *private_decl = NULL;
|
||||
bool path_found = false;
|
||||
Decl *decl = NULL;
|
||||
const char *symbol_str = TOKSTR(symbol);
|
||||
if (path)
|
||||
@@ -253,14 +252,12 @@ Decl *sema_resolve_parameterized_symbol(Context *context, TokenId symbol, Path *
|
||||
Decl *import = context->imports[i];
|
||||
|
||||
// Skip any without parameters.
|
||||
if (!import->module->parameters) continue;
|
||||
if (!import->module->is_generic) continue;
|
||||
|
||||
// 5. Can we match a subpath?
|
||||
if (path->len > import->import.path->len) continue;
|
||||
if (!matches_subpath(import->import.path, path)) continue;
|
||||
|
||||
// 6. We have a sub path match at least.
|
||||
path_found = true;
|
||||
|
||||
// 7. Find the symbol
|
||||
Decl *found = module_find_symbol(import->module, symbol_str);
|
||||
@@ -302,7 +299,7 @@ Decl *sema_resolve_parameterized_symbol(Context *context, TokenId symbol, Path *
|
||||
Decl *import = context->imports[i];
|
||||
|
||||
// Skip any without parameters.
|
||||
if (!import->module->parameters) continue;
|
||||
if (!import->module->is_generic) continue;
|
||||
|
||||
// 7. Find the symbol
|
||||
Decl *found = module_find_symbol(import->module, symbol_str);
|
||||
|
||||
@@ -508,8 +508,25 @@ static inline bool sema_analyse_local_decl(Context *context, Decl *decl)
|
||||
SEMA_ERROR(decl->var.init_expr, "A failable expression was expected here.");
|
||||
return decl_poison(decl);
|
||||
}
|
||||
|
||||
}
|
||||
EXIT_OK:
|
||||
if (decl->var.is_static)
|
||||
{
|
||||
scratch_buffer_clear();
|
||||
scratch_buffer_append(context->active_function_for_analysis->name);
|
||||
scratch_buffer_append_char('.');
|
||||
scratch_buffer_append(decl->name);
|
||||
decl->external_name = scratch_buffer_interned();
|
||||
}
|
||||
if (decl->var.init_expr && decl->var.is_static)
|
||||
{
|
||||
if (!expr_is_constant_eval(decl->var.init_expr))
|
||||
{
|
||||
SEMA_ERROR(decl->var.init_expr, "Static variable initialization must be constant.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!decl->alignment) decl->alignment = type_alloca_alignment(decl->type);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
#define COMPILER_VERSION "A225"
|
||||
#define COMPILER_VERSION "A226"
|
||||
@@ -6,7 +6,7 @@ struct Foo
|
||||
int x, y;
|
||||
}
|
||||
|
||||
static Foo[10] array;
|
||||
private Foo[10] array;
|
||||
|
||||
// #expect: test.ll
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ struct Connection
|
||||
long length;
|
||||
}
|
||||
|
||||
static Connection[3] link
|
||||
private Connection[3] link
|
||||
= { {1, "link1", 10},
|
||||
{2, "link2", 20},
|
||||
{3, "link3", 30} };
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
static const char AA = ~(char)(0);
|
||||
private const char AA = ~(char)(0);
|
||||
const char BB = 200 ;
|
||||
static const uint CC = ~(uint)(0);
|
||||
static const uint DD = FOO;
|
||||
private const uint CC = ~(uint)(0);
|
||||
private const uint DD = FOO;
|
||||
|
||||
static const FOO = ~(uint)(0);
|
||||
private const FOO = ~(uint)(0);
|
||||
|
||||
static uint x = AA;
|
||||
static uint z = CC;
|
||||
static char w = (char)(FOO);
|
||||
static ushort v = (ushort)(FOO);
|
||||
static uint z2 = DD;
|
||||
private uint x = AA;
|
||||
private uint z = CC;
|
||||
private char w = (char)(FOO);
|
||||
private ushort v = (ushort)(FOO);
|
||||
private uint z2 = DD;
|
||||
|
||||
func void test()
|
||||
{
|
||||
|
||||
20
test/test_suite/functions/static_vars.c3t
Normal file
20
test/test_suite/functions/static_vars.c3t
Normal file
@@ -0,0 +1,20 @@
|
||||
module foo;
|
||||
|
||||
func int test()
|
||||
{
|
||||
static int x = 1;
|
||||
x++;
|
||||
return x;
|
||||
}
|
||||
|
||||
// #expect: foo.ll
|
||||
|
||||
@test.x = hidden global i32 1, align 4
|
||||
|
||||
define i32 @foo.test()
|
||||
|
||||
%0 = load i32, i32* @test.x, align 4
|
||||
%add = add i32 %0, 1
|
||||
store i32 %add, i32* @test.x, align 4
|
||||
%1 = load i32, i32* @test.x, align 4
|
||||
ret i32 %1
|
||||
1
test/test_suite/globals/static_global.c3
Normal file
1
test/test_suite/globals/static_global.c3
Normal file
@@ -0,0 +1 @@
|
||||
static int ifej; // #error: Expected a top level declaration here
|
||||
@@ -4,7 +4,7 @@ macro int cofefe(#a)
|
||||
return #a + #a;
|
||||
}
|
||||
|
||||
static int abc = 1;
|
||||
private int abc = 1;
|
||||
|
||||
func int xx()
|
||||
{
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
module const_pointer;
|
||||
|
||||
static double foo = 17;
|
||||
static double bar = 12.0;
|
||||
static float xx = 12.0;
|
||||
private double foo = 17;
|
||||
private double bar = 12.0;
|
||||
private float xx = 12.0;
|
||||
|
||||
static void*[3] data = { &foo, &bar, &xx };
|
||||
private void*[3] data = { &foo, &bar, &xx };
|
||||
|
||||
// #expect: const_pointer.ll
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module test;
|
||||
|
||||
static Foo a;
|
||||
private Foo a;
|
||||
|
||||
struct Foo
|
||||
{
|
||||
|
||||
@@ -6,16 +6,16 @@ struct Foo
|
||||
long bar;
|
||||
}
|
||||
|
||||
static usize x = Foo.sizeof;
|
||||
private usize x = Foo.sizeof;
|
||||
|
||||
static Foo foo1 = { 1, 2 };
|
||||
static Foo foo2 = { .foo = 2 };
|
||||
static Foo foo3 = { .bar = 3 };
|
||||
static Foo foo4 = { .bar = 4, .foo = 4, .bar = 1 };
|
||||
static Foo foo5 = {};
|
||||
static Foo foo6;
|
||||
static const Foo FOO7 = { 1, 2 };
|
||||
static Foo foo8 = FOO7;
|
||||
private Foo foo1 = { 1, 2 };
|
||||
private Foo foo2 = { .foo = 2 };
|
||||
private Foo foo3 = { .bar = 3 };
|
||||
private Foo foo4 = { .bar = 4, .foo = 4, .bar = 1 };
|
||||
private Foo foo5 = {};
|
||||
private Foo foo6;
|
||||
private const Foo FOO7 = { 1, 2 };
|
||||
private Foo foo8 = FOO7;
|
||||
|
||||
// #expect: structo.ll
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ union Foo
|
||||
double b;
|
||||
}
|
||||
|
||||
static Foo f = { .a = 23 };
|
||||
static Foo g = { .b = 2.3 };
|
||||
static Foo h = { .a = 23, .b = 2.3 };
|
||||
private Foo f = { .a = 23 };
|
||||
private Foo g = { .b = 2.3 };
|
||||
private Foo h = { .a = 23, .b = 2.3 };
|
||||
Foo i = { .b = 2.3, .a = 23 };
|
||||
|
||||
// #expect: test.ll
|
||||
|
||||
@@ -17,7 +17,7 @@ import bar;
|
||||
|
||||
int a;
|
||||
|
||||
static func void test2()
|
||||
private func void test2()
|
||||
{
|
||||
int c = a; // This is fine.
|
||||
c = foo::b;
|
||||
|
||||
@@ -11,7 +11,7 @@ func void runBar()
|
||||
module baz;
|
||||
import bar;
|
||||
|
||||
static func void visible()
|
||||
private func void visible()
|
||||
{
|
||||
bar::barFunc();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ func void runBar()
|
||||
// #file: file2.c3
|
||||
module bar;
|
||||
|
||||
static func void notVisible()
|
||||
private func void notVisible()
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
module foo;
|
||||
|
||||
static func void hidden()
|
||||
private func void hidden()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ func void runBar()
|
||||
// #file: file2.c3
|
||||
module baz;
|
||||
|
||||
static func void visible()
|
||||
private func void visible()
|
||||
{
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user