commit
4a8b224c19
3
TODOLIST
3
TODOLIST
@ -1,5 +1,4 @@
|
|||||||
pointers
|
|
||||||
function pointers
|
function pointers
|
||||||
for loops
|
for loops
|
||||||
arrays
|
struct
|
||||||
c abi
|
c abi
|
||||||
|
@ -157,6 +157,7 @@ const LocalSymbol = struct {
|
|||||||
attributes: Attributes = .{},
|
attributes: Attributes = .{},
|
||||||
local_declaration: LocalDeclaration,
|
local_declaration: LocalDeclaration,
|
||||||
type: *Type,
|
type: *Type,
|
||||||
|
appointee_type: ?*Type = null,
|
||||||
instruction: Instruction,
|
instruction: Instruction,
|
||||||
alignment: u32,
|
alignment: u32,
|
||||||
|
|
||||||
@ -622,6 +623,7 @@ const Parser = struct{
|
|||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
'[' => {
|
'[' => {
|
||||||
|
// This is an array expression
|
||||||
parser.i += 1;
|
parser.i += 1;
|
||||||
|
|
||||||
const ty = maybe_type orelse exit(1);
|
const ty = maybe_type orelse exit(1);
|
||||||
@ -905,17 +907,20 @@ const Parser = struct{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
' ', ',', ';', ')' => {
|
'&' => {
|
||||||
const declaration_value = switch (lookup_result.declaration.*.id) {
|
parser.i += 1;
|
||||||
.local => block: {
|
|
||||||
const local_declaration = lookup_result.declaration.*.get_payload(.local);
|
const local_declaration = lookup_result.declaration.*.get_payload(.local);
|
||||||
const local_symbol = local_declaration.to_symbol();
|
const local_symbol = local_declaration.to_symbol();
|
||||||
|
return &local_symbol.instruction.value;
|
||||||
|
},
|
||||||
|
'@' => {
|
||||||
|
parser.i += 1;
|
||||||
|
|
||||||
if (maybe_type) |ty| {
|
const local_declaration = lookup_result.declaration.*.get_payload(.local);
|
||||||
switch (typecheck(ty, local_symbol.type)) {
|
const local_symbol = local_declaration.to_symbol();
|
||||||
.success => {},
|
assert(local_symbol.type.sema.id == .pointer);
|
||||||
}
|
assert(local_symbol.appointee_type != null);
|
||||||
}
|
|
||||||
|
|
||||||
const load = thread.loads.append(.{
|
const load = thread.loads.append(.{
|
||||||
.instruction = .{
|
.instruction = .{
|
||||||
@ -934,7 +939,73 @@ const Parser = struct{
|
|||||||
.is_volatile = false,
|
.is_volatile = false,
|
||||||
});
|
});
|
||||||
_ = analyzer.current_basic_block.instructions.append(&load.instruction);
|
_ = analyzer.current_basic_block.instructions.append(&load.instruction);
|
||||||
break :block &load.instruction.value;
|
|
||||||
|
return switch (side) {
|
||||||
|
.left => &load.instruction.value,
|
||||||
|
.right => block: {
|
||||||
|
const pointer_load_type = local_symbol.appointee_type.?;
|
||||||
|
if (maybe_type) |ty| {
|
||||||
|
switch (typecheck(ty, pointer_load_type)) {
|
||||||
|
.success => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pointer_load = thread.loads.append(.{
|
||||||
|
.instruction = .{
|
||||||
|
.value = .{
|
||||||
|
.sema = .{
|
||||||
|
.thread = thread.get_index(),
|
||||||
|
.resolved = true,
|
||||||
|
.id = .instruction,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.id = .load,
|
||||||
|
},
|
||||||
|
.value = &load.instruction.value,
|
||||||
|
.type = pointer_load_type,
|
||||||
|
.alignment = pointer_load_type.alignment,
|
||||||
|
.is_volatile = false,
|
||||||
|
});
|
||||||
|
_ = analyzer.current_basic_block.instructions.append(&pointer_load.instruction);
|
||||||
|
break :block &pointer_load.instruction.value;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
' ', ',', ';', ')' => {
|
||||||
|
const declaration_value = switch (lookup_result.declaration.*.id) {
|
||||||
|
.local => block: {
|
||||||
|
const local_declaration = lookup_result.declaration.*.get_payload(.local);
|
||||||
|
const local_symbol = local_declaration.to_symbol();
|
||||||
|
|
||||||
|
if (maybe_type) |ty| {
|
||||||
|
switch (typecheck(ty, local_symbol.type)) {
|
||||||
|
.success => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break :block switch (side) {
|
||||||
|
.right => b: {
|
||||||
|
const load = thread.loads.append(.{
|
||||||
|
.instruction = .{
|
||||||
|
.value = .{
|
||||||
|
.sema = .{
|
||||||
|
.thread = thread.get_index(),
|
||||||
|
.resolved = true,
|
||||||
|
.id = .instruction,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.id = .load,
|
||||||
|
},
|
||||||
|
.value = &local_symbol.instruction.value,
|
||||||
|
.type = local_symbol.type,
|
||||||
|
.alignment = local_symbol.type.alignment,
|
||||||
|
.is_volatile = false,
|
||||||
|
});
|
||||||
|
_ = analyzer.current_basic_block.instructions.append(&load.instruction);
|
||||||
|
break :b &load.instruction.value;
|
||||||
|
},
|
||||||
|
.left => &local_symbol.instruction.value,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
.argument => block: {
|
.argument => block: {
|
||||||
const argument_declaration = lookup_result.declaration.*.get_payload(.argument);
|
const argument_declaration = lookup_result.declaration.*.get_payload(.argument);
|
||||||
@ -944,6 +1015,8 @@ const Parser = struct{
|
|||||||
.success => {},
|
.success => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break :block switch (side) {
|
||||||
|
.right => b: {
|
||||||
const load = thread.loads.append(.{
|
const load = thread.loads.append(.{
|
||||||
.instruction = .{
|
.instruction = .{
|
||||||
.value = .{
|
.value = .{
|
||||||
@ -961,7 +1034,10 @@ const Parser = struct{
|
|||||||
.is_volatile = false,
|
.is_volatile = false,
|
||||||
});
|
});
|
||||||
_ = analyzer.current_basic_block.instructions.append(&load.instruction);
|
_ = analyzer.current_basic_block.instructions.append(&load.instruction);
|
||||||
break :block &load.instruction.value;
|
break :b &load.instruction.value;
|
||||||
|
},
|
||||||
|
.left => &argument_symbol.instruction.value,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
.global => block: {
|
.global => block: {
|
||||||
const global_declaration = lookup_result.declaration.*.get_payload(.global);
|
const global_declaration = lookup_result.declaration.*.get_payload(.global);
|
||||||
@ -981,6 +1057,8 @@ const Parser = struct{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break :block switch (side) {
|
||||||
|
.right => b: {
|
||||||
const load = thread.loads.append(.{
|
const load = thread.loads.append(.{
|
||||||
.instruction = .{
|
.instruction = .{
|
||||||
.value = .{
|
.value = .{
|
||||||
@ -998,12 +1076,18 @@ const Parser = struct{
|
|||||||
.is_volatile = false,
|
.is_volatile = false,
|
||||||
});
|
});
|
||||||
_ = analyzer.current_basic_block.instructions.append(&load.instruction);
|
_ = analyzer.current_basic_block.instructions.append(&load.instruction);
|
||||||
break :block &load.instruction.value;
|
break :b &load.instruction.value;
|
||||||
|
},
|
||||||
|
.left => &global_symbol.value,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const declaration_type = declaration_value.get_type();
|
const declaration_type = declaration_value.get_type();
|
||||||
|
|
||||||
|
if (unary != .none) assert(side == .right);
|
||||||
|
if (side == .left) assert(side == .left);
|
||||||
|
|
||||||
return switch (unary) {
|
return switch (unary) {
|
||||||
.none => declaration_value,
|
.none => declaration_value,
|
||||||
.one_complement => block: {
|
.one_complement => block: {
|
||||||
@ -1119,6 +1203,10 @@ const Parser = struct{
|
|||||||
var it_ty: ?*Type = ty;
|
var it_ty: ?*Type = ty;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
// if (src[parser.i] == ';') {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
if (iterations == 1 and it_ty == null) {
|
if (iterations == 1 and it_ty == null) {
|
||||||
it_ty = previous_value.get_type();
|
it_ty = previous_value.get_type();
|
||||||
}
|
}
|
||||||
@ -1724,6 +1812,9 @@ const Value = struct {
|
|||||||
const lz = instruction.get_payload(.leading_zeroes);
|
const lz = instruction.get_payload(.leading_zeroes);
|
||||||
return lz.value.get_type();
|
return lz.value.get_type();
|
||||||
},
|
},
|
||||||
|
.local_symbol => {
|
||||||
|
return &instance.threads[value.sema.thread].pointer;
|
||||||
|
},
|
||||||
else => |t| @panic(@tagName(t)),
|
else => |t| @panic(@tagName(t)),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -1731,6 +1822,16 @@ const Value = struct {
|
|||||||
const constant_int = value.get_payload(.constant_int);
|
const constant_int = value.get_payload(.constant_int);
|
||||||
return constant_int.type;
|
return constant_int.type;
|
||||||
},
|
},
|
||||||
|
.global_symbol => {
|
||||||
|
const global_symbol = value.get_payload(.global_symbol);
|
||||||
|
return switch (global_symbol.id) {
|
||||||
|
.global_variable => b: {
|
||||||
|
const global_variable = global_symbol.get_payload(.global_variable);
|
||||||
|
break :b global_variable.type;
|
||||||
|
},
|
||||||
|
else => |t| @panic(@tagName(t)),
|
||||||
|
};
|
||||||
|
},
|
||||||
else => |t| @panic(@tagName(t)),
|
else => |t| @panic(@tagName(t)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1753,6 +1854,7 @@ const Type = struct {
|
|||||||
void,
|
void,
|
||||||
integer,
|
integer,
|
||||||
array,
|
array,
|
||||||
|
pointer,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Integer = struct {
|
const Integer = struct {
|
||||||
@ -1781,6 +1883,7 @@ const Type = struct {
|
|||||||
.void = void,
|
.void = void,
|
||||||
.integer = Integer,
|
.integer = Integer,
|
||||||
.array = Array,
|
.array = Array,
|
||||||
|
.pointer = void,
|
||||||
});
|
});
|
||||||
|
|
||||||
fn get_payload(ty: *Type, comptime id: Id) *id_to_type_map.get(id) {
|
fn get_payload(ty: *Type, comptime id: Id) *id_to_type_map.get(id) {
|
||||||
@ -2277,6 +2380,15 @@ const Thread = struct{
|
|||||||
.size = 0,
|
.size = 0,
|
||||||
.alignment = 0,
|
.alignment = 0,
|
||||||
},
|
},
|
||||||
|
pointer: Type = .{
|
||||||
|
.sema = .{
|
||||||
|
.thread = undefined,
|
||||||
|
.id = .pointer,
|
||||||
|
.resolved = true,
|
||||||
|
},
|
||||||
|
.size = 8,
|
||||||
|
.alignment = 8,
|
||||||
|
},
|
||||||
handle: std.Thread = undefined,
|
handle: std.Thread = undefined,
|
||||||
|
|
||||||
fn add_thread_work(thread: *Thread, job: Job) void {
|
fn add_thread_work(thread: *Thread, job: Job) void {
|
||||||
@ -3833,8 +3945,13 @@ fn llvm_get_type(thread: *Thread, ty: *Type) *LLVM.Type {
|
|||||||
const array_type = LLVM.Type.Array.get(element_type, array_ty.descriptor.element_count);
|
const array_type = LLVM.Type.Array.get(element_type, array_ty.descriptor.element_count);
|
||||||
break :b array_type.toType();
|
break :b array_type.toType();
|
||||||
},
|
},
|
||||||
|
.pointer => b: {
|
||||||
|
const pointer_type = thread.llvm.context.getPointerType(0);
|
||||||
|
break :b pointer_type.toType();
|
||||||
|
},
|
||||||
else => |t| @panic(@tagName(t)),
|
else => |t| @panic(@tagName(t)),
|
||||||
};
|
};
|
||||||
|
|
||||||
return llvm_type;
|
return llvm_type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4053,6 +4170,7 @@ pub fn analyze_local_block(thread: *Thread, analyzer: *Analyzer, parser: *Parser
|
|||||||
initial_value: *Value,
|
initial_value: *Value,
|
||||||
type: *Type,
|
type: *Type,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result: LocalResult = switch (src[parser.i]) {
|
const result: LocalResult = switch (src[parser.i]) {
|
||||||
':' => block: {
|
':' => block: {
|
||||||
parser.i += 1;
|
parser.i += 1;
|
||||||
@ -4115,6 +4233,22 @@ pub fn analyze_local_block(thread: *Thread, analyzer: *Analyzer, parser: *Parser
|
|||||||
.alignment = result.type.alignment,
|
.alignment = result.type.alignment,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (local_symbol.type.sema.id == .pointer) {
|
||||||
|
switch (result.initial_value.sema.id) {
|
||||||
|
.instruction => {
|
||||||
|
const instruction = result.initial_value.get_payload(.instruction);
|
||||||
|
switch (instruction.id) {
|
||||||
|
.local_symbol => {
|
||||||
|
const right_local_symbol = instruction.get_payload(.local_symbol);
|
||||||
|
local_symbol.appointee_type = right_local_symbol.type;
|
||||||
|
},
|
||||||
|
else => |t| @panic(@tagName(t)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => |t| @panic(@tagName(t)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ = analyzer.current_function.stack_slots.append(local_symbol);
|
_ = analyzer.current_function.stack_slots.append(local_symbol);
|
||||||
|
|
||||||
const store = thread.stores.append(.{
|
const store = thread.stores.append(.{
|
||||||
@ -4290,21 +4424,9 @@ pub fn analyze_local_block(thread: *Thread, analyzer: *Analyzer, parser: *Parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (statement_start_ch_index == parser.i) {
|
if (statement_start_ch_index == parser.i) {
|
||||||
if (is_identifier_char_start(statement_start_ch)) {
|
const left = parser.parse_single_expression(analyzer, thread, file, null, .left);
|
||||||
const raw_identifier = parser.parse_raw_identifier(src);
|
|
||||||
const keyword_index = parse_keyword(raw_identifier);
|
|
||||||
const is_keyword = keyword_index != ~@as(u32, 0);
|
|
||||||
if (is_keyword) {
|
|
||||||
const keyword: Keyword = @enumFromInt(keyword_index);
|
|
||||||
switch (keyword) {
|
|
||||||
else => |t| @panic(@tagName(t)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const identifier = intern_identifier(&thread.identifiers, raw_identifier);
|
|
||||||
parser.skip_space(src);
|
parser.skip_space(src);
|
||||||
|
|
||||||
const declaration_lookup = analyzer.current_scope.get_declaration(identifier) orelse unreachable;
|
|
||||||
|
|
||||||
const operation_index = parser.i;
|
const operation_index = parser.i;
|
||||||
const operation_ch = src[operation_index];
|
const operation_ch = src[operation_index];
|
||||||
var is_binary_operation = false;
|
var is_binary_operation = false;
|
||||||
@ -4324,34 +4446,74 @@ pub fn analyze_local_block(thread: *Thread, analyzer: *Analyzer, parser: *Parser
|
|||||||
|
|
||||||
parser.skip_space(src);
|
parser.skip_space(src);
|
||||||
|
|
||||||
const declaration_value = get_declaration_value(analyzer, thread, declaration_lookup.declaration.*, null, .right);
|
const expected_right_type = switch (left.sema.id) {
|
||||||
const declaration_type = switch (declaration_lookup.declaration.*.id) {
|
.instruction => b: {
|
||||||
.argument => block: {
|
const instruction = left.get_payload(.instruction);
|
||||||
const argument_declaration = declaration_lookup.declaration.*.get_payload(.argument);
|
switch (instruction.id) {
|
||||||
const argument_symbol = argument_declaration.to_symbol();
|
.load => {
|
||||||
break :block argument_symbol.type;
|
const load = instruction.get_payload(.load);
|
||||||
},
|
switch (load.value.sema.id) {
|
||||||
.local => block: {
|
.instruction => {
|
||||||
const local_declaration = declaration_lookup.declaration.*.get_payload(.local);
|
const load_instruction = load.value.get_payload(.instruction);
|
||||||
const local_symbol = local_declaration.to_symbol();
|
switch (load_instruction.id) {
|
||||||
break :block local_symbol.type;
|
.local_symbol => {
|
||||||
},
|
const local_symbol = load_instruction.get_payload(.local_symbol);
|
||||||
.global => block: {
|
if (local_symbol.type.sema.id == .pointer) {
|
||||||
const global_declaration = declaration_lookup.declaration.*.get_payload(.global);
|
break :b local_symbol.appointee_type.?;
|
||||||
const global_symbol = global_declaration.to_symbol();
|
} else {
|
||||||
switch (global_symbol.id) {
|
break :b local_symbol.type;
|
||||||
.global_variable => {
|
}
|
||||||
const global_variable = global_symbol.get_payload(.global_variable);
|
|
||||||
break :block global_variable.type;
|
|
||||||
},
|
},
|
||||||
else => |t| @panic(@tagName(t)),
|
else => |t| @panic(@tagName(t)),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
else => |t| @panic(@tagName(t)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.local_symbol => {
|
||||||
|
const local_symbol = instruction.get_payload(.local_symbol);
|
||||||
|
assert(local_symbol.appointee_type == null);
|
||||||
|
break :b local_symbol.type;
|
||||||
|
},
|
||||||
|
else => |t| @panic(@tagName(t)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.global_symbol => b: {
|
||||||
|
const global_symbol = left.get_payload(.global_symbol);
|
||||||
|
break :b switch (global_symbol.id) {
|
||||||
|
.global_variable => gv: {
|
||||||
|
const global_variable = global_symbol.get_payload(.global_variable);
|
||||||
|
break :gv global_variable.type;
|
||||||
|
},
|
||||||
|
else => |t| @panic(@tagName(t)),
|
||||||
};
|
};
|
||||||
const right = parser.parse_expression(analyzer, thread, file, declaration_type, .right);
|
},
|
||||||
|
else => |t| @panic(@tagName(t)),
|
||||||
|
};
|
||||||
|
|
||||||
const source = switch (is_binary_operation) {
|
const source = switch (is_binary_operation) {
|
||||||
false => right,
|
false => parser.parse_expression(analyzer, thread, file, expected_right_type, .right),
|
||||||
true => block: {
|
true => block: {
|
||||||
|
const left_load = thread.loads.append(.{
|
||||||
|
.instruction = .{
|
||||||
|
.id = .load,
|
||||||
|
.value = .{
|
||||||
|
.sema = .{
|
||||||
|
.thread = thread.get_index(),
|
||||||
|
.resolved = true,
|
||||||
|
.id = .instruction,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.value = left,
|
||||||
|
.type = expected_right_type,
|
||||||
|
.alignment = expected_right_type.alignment,
|
||||||
|
.is_volatile = false,
|
||||||
|
});
|
||||||
|
_ = analyzer.current_basic_block.instructions.append(&left_load.instruction);
|
||||||
|
|
||||||
|
const right = parser.parse_expression(analyzer, thread, file, expected_right_type, .right);
|
||||||
|
|
||||||
const binary_operation_id: IntegerBinaryOperation.Id = switch (operation_ch) {
|
const binary_operation_id: IntegerBinaryOperation.Id = switch (operation_ch) {
|
||||||
'+' => .add,
|
'+' => .add,
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
@ -4368,20 +4530,17 @@ pub fn analyze_local_block(thread: *Thread, analyzer: *Analyzer, parser: *Parser
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.left = declaration_value,
|
.left = &left_load.instruction.value,
|
||||||
.right = right,
|
.right = right,
|
||||||
.type = declaration_type,
|
.type = expected_right_type,
|
||||||
});
|
});
|
||||||
_ = analyzer.current_basic_block.instructions.append(&binary_operation.instruction);
|
_ = analyzer.current_basic_block.instructions.append(&binary_operation.instruction);
|
||||||
break :block &binary_operation.instruction.value;
|
break :block &binary_operation.instruction.value;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
parser.skip_space(src);
|
parser.skip_space(src);
|
||||||
|
|
||||||
parser.expect_character(src, ';');
|
parser.expect_character(src, ';');
|
||||||
|
|
||||||
const declaration_pointer = get_declaration_value(analyzer, thread, declaration_lookup.declaration.*, null, .left);
|
|
||||||
const store = thread.stores.append(.{
|
const store = thread.stores.append(.{
|
||||||
.instruction = .{
|
.instruction = .{
|
||||||
.id = .store,
|
.id = .store,
|
||||||
@ -4393,9 +4552,9 @@ pub fn analyze_local_block(thread: *Thread, analyzer: *Analyzer, parser: *Parser
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.destination = declaration_pointer,
|
.destination = left,
|
||||||
.source = source,
|
.source = source,
|
||||||
.alignment = declaration_type.alignment,
|
.alignment = expected_right_type.alignment,
|
||||||
.is_volatile = false,
|
.is_volatile = false,
|
||||||
});
|
});
|
||||||
_ = analyzer.current_basic_block.instructions.append(&store.instruction);
|
_ = analyzer.current_basic_block.instructions.append(&store.instruction);
|
||||||
@ -4403,10 +4562,6 @@ pub fn analyze_local_block(thread: *Thread, analyzer: *Analyzer, parser: *Parser
|
|||||||
else => @panic((src.ptr + parser.i)[0..1]),
|
else => @panic((src.ptr + parser.i)[0..1]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
exit_with_error("Unrecognized statement initial char");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parser.expect_character(src, brace_close);
|
parser.expect_character(src, brace_close);
|
||||||
|
6
retest/standalone/pointer/main.nat
Normal file
6
retest/standalone/pointer/main.nat
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
fn[cc(.c)] main[export]() s32 {
|
||||||
|
>a: s32 = 0;
|
||||||
|
>pointer = a&;
|
||||||
|
pointer@ = 4;
|
||||||
|
return pointer@ - a;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user