Restructure a bit
This commit is contained in:
483
src/parser.c
483
src/parser.c
@@ -25,271 +25,298 @@ statement:
|
|||||||
| statement;
|
| statement;
|
||||||
statement
|
statement
|
||||||
: expr
|
: expr
|
||||||
| assignment;
|
| assignment;
|
||||||
assignment
|
assignment
|
||||||
: IDENTIFIER TYPE ASSIGNMENT expr
|
: IDENTIFIER TYPE ASSIGNMENT expr
|
||||||
expr
|
expr
|
||||||
: INT OPERATOR INT {$$ = binary_op_int($1, $3, $2);}
|
: INT OPERATOR INT {$$ = binary_op_int($1, $3, $2);}
|
||||||
| INT OPERATOR FLOAT {conv($1); $$ = binary_op_float($1, $3, $2);}
|
| INT OPERATOR FLOAT {conv($1); $$ = binary_op_float($1, $3, $2);}
|
||||||
| FLOAT OPERATOR INT {conv($2); $$ = binary_op_float($1, $3, $2);}
|
| FLOAT OPERATOR INT {conv($2); $$ = binary_op_float($1, $3, $2);}
|
||||||
| FLOAT OPERATOR FLOAT {$$ = binary_op_float($1, $3, $2);};
|
| FLOAT OPERATOR FLOAT {$$ = binary_op_float($1, $3, $2);};
|
||||||
|
| INT
|
||||||
|
| FLOAT
|
||||||
============================= */
|
============================= */
|
||||||
|
|
||||||
enum value_type {
|
enum value_type {
|
||||||
VALUE_INTEGER,
|
VALUE_INTEGER,
|
||||||
VALUE_OPERATOR,
|
VALUE_OPERATOR,
|
||||||
VALUE_FLOATING,
|
VALUE_FLOATING,
|
||||||
VALUE_TYPE_COUNT,
|
VALUE_TYPE_COUNT,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const char* value_type_str[VALUE_TYPE_COUNT] = {
|
static const char* value_type_str[VALUE_TYPE_COUNT] = {
|
||||||
[VALUE_INTEGER] = "VALUE_INTEGER",
|
[VALUE_INTEGER] = "VALUE_INTEGER",
|
||||||
[VALUE_OPERATOR] = "VALUE_OPERATOR",
|
[VALUE_OPERATOR] = "VALUE_OPERATOR",
|
||||||
[VALUE_FLOATING] = "VALUE_FLOATING",
|
[VALUE_FLOATING] = "VALUE_FLOATING",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const int operator_precedence[256] = {
|
||||||
|
['+'] = 0,
|
||||||
|
['-'] = 0,
|
||||||
|
['*'] = 10,
|
||||||
|
['/'] = 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct parser {
|
||||||
|
Token* cur;
|
||||||
|
Token* next;
|
||||||
|
Mfile* m;
|
||||||
|
} Parser;
|
||||||
|
|
||||||
|
bool parser_advance(Error* err, Parser* p)
|
||||||
|
{
|
||||||
|
p->cur = p->next;
|
||||||
|
mfile_skip(p->m, isspace);
|
||||||
|
p->next = token_read(err, p->m);
|
||||||
|
if (!error_empty(err)) {
|
||||||
|
error_push(err, "%s failed", __func__);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct value {
|
typedef struct value {
|
||||||
const char* debug_name;
|
|
||||||
enum value_type type;
|
|
||||||
union {
|
|
||||||
int64_t i64;
|
|
||||||
char op[3];
|
|
||||||
double f64;
|
|
||||||
};
|
|
||||||
} Value;
|
|
||||||
|
|
||||||
typedef struct symbol_table_entry {
|
|
||||||
const char* debug_name;
|
const char* debug_name;
|
||||||
int id;
|
enum value_type type;
|
||||||
Value* val;
|
union {
|
||||||
struct symbol_table_entry* next;
|
int64_t i64;
|
||||||
} Symbol_table_entry;
|
double f64;
|
||||||
|
char op[3];
|
||||||
typedef struct symbol_table {
|
};
|
||||||
Symbol_table_entry* syms;
|
} Value;
|
||||||
} Symbol_table;
|
|
||||||
|
|
||||||
static Value* parse_operator(Error* err, Token* t)
|
static Value* parse_operator(Error* err, Token* t)
|
||||||
{
|
{
|
||||||
if (t->type != TOKEN_OPERATOR) {
|
if (t->type != TOKEN_OPERATOR) {
|
||||||
error_push(err, "%s: unexpected token type: %s", __func__, token_type_str[t->type]);
|
error_push(err, "%s: unexpected token type: %s", __func__, token_type_str[t->type]);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
Value* v = malloc(sizeof *v);
|
Value* v = calloc(1, sizeof *v);
|
||||||
if (!v) {
|
if (!v) {
|
||||||
error_push(err, "%s: failed to allocate value: %s", __func__, strerror(errno));
|
error_push(err, "%s: failed to allocate value: %s", __func__, strerror(errno));
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
v->type = VALUE_OPERATOR;
|
v->type = VALUE_OPERATOR;
|
||||||
strncpy(v->op, t->start, MIN(t->end - t->start, sizeof(v->op)));
|
strncpy(v->op, t->start, MIN(t->end - t->start, sizeof(v->op)));
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value* parse_int(Error* err, Token* t)
|
static Value* parse_int(Error* err, Token* t)
|
||||||
{
|
{
|
||||||
if (t->type != TOKEN_INTEGER) {
|
if (t->type != TOKEN_INTEGER) {
|
||||||
error_push(err, "%s: unexpected token type: %s", __func__, token_type_str[t->type]);
|
error_push(err, "%s: unexpected token type: %s", __func__, token_type_str[t->type]);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
Value* v = malloc(sizeof *v);
|
Value* v = calloc(1, sizeof *v);
|
||||||
if (!v) {
|
if (!v) {
|
||||||
error_push(err, "%s: failed to allocate value: %s", __func__, strerror(errno));
|
error_push(err, "%s: failed to allocate value: %s", __func__, strerror(errno));
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
v->type = VALUE_INTEGER;
|
v->type = VALUE_INTEGER;
|
||||||
assert(errno == 0);
|
assert(errno == 0);
|
||||||
errno = 0;
|
errno = 0;
|
||||||
v->i64= strtol(t->start, NULL, 10);
|
v->i64= strtol(t->start, NULL, 10);
|
||||||
if (errno != 0) {
|
if (errno != 0) {
|
||||||
error_push(err, "%s: failed to parse int: %s", __func__, strerror(errno));
|
error_push(err, "%s: failed to parse int: %s", __func__, strerror(errno));
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value* parse_floating(Error* err, Token* t)
|
static Value* parse_floating(Error* err, Token* t)
|
||||||
{
|
{
|
||||||
if (t->type != TOKEN_FLOATING) {
|
if (t->type != TOKEN_FLOATING) {
|
||||||
error_push(err, "%s: unexpected token type: %s", __func__, token_type_str[t->type]);
|
error_push(err, "(%s) unexpected token type: %s", __func__, token_type_str[t->type]);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
Value* v = malloc(sizeof *v);
|
Value* v = calloc(1, sizeof *v);
|
||||||
if (!v) {
|
if (!v) {
|
||||||
error_push(err, "%s: failed to allocate value: %s", __func__, strerror(errno));
|
error_push(err, "(%s) failed to allocate value: %s", __func__, strerror(errno));
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
v->type = VALUE_FLOATING;
|
v->type = VALUE_FLOATING;
|
||||||
assert(errno == 0);
|
assert(errno == 0);
|
||||||
errno = 0;
|
errno = 0;
|
||||||
v->f64= strtod(t->start, NULL);
|
v->f64= strtod(t->start, NULL);
|
||||||
if (errno != 0) {
|
if (errno != 0) {
|
||||||
error_push(err, "%s: failed to parse float: %s", __func__, strerror(errno));
|
error_push(err, "(%s) failed to parse float: %s", __func__, strerror(errno));
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value* parse_number(Error* err, Token* t)
|
static Value* parse_number(Error* err, Token* t)
|
||||||
{
|
{
|
||||||
switch (t->type) {
|
switch (t->type) {
|
||||||
case TOKEN_FLOATING:
|
case TOKEN_FLOATING:
|
||||||
return parse_floating(err, t);
|
return parse_floating(err, t);
|
||||||
case TOKEN_INTEGER:
|
case TOKEN_INTEGER:
|
||||||
return parse_int(err, t);
|
return parse_int(err, t);
|
||||||
default:
|
default:
|
||||||
error_push(err, "(%s) unexpected token type %s", __func__, token_type_str[t->type]);
|
error_push(err, "(%s) unexpected token type %s", __func__, token_type_str[t->type]);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void conv_int_to_float(Error* err, Value* val)
|
static void conv_int_to_float(Error* err, Value* val)
|
||||||
{
|
{
|
||||||
if (val->type != VALUE_INTEGER) {
|
if (val->type != VALUE_INTEGER) {
|
||||||
error_push(err, "(%s) conversion from %s to float not implemented", __func__, value_type_str[val->type]);
|
error_push(err, "(%s) conversion from %s to float not implemented", __func__, value_type_str[val->type]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fprintf(stderr, "converting %ld to %lf", val->i64, (double)val->i64);
|
fprintf(stderr, "converting %ld to %lf", val->i64, (double)val->i64);
|
||||||
val->f64 = (double)val->i64;
|
val->f64 = (double)val->i64;
|
||||||
val->type = VALUE_FLOATING;
|
val->type = VALUE_FLOATING;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value* parse_binary_expr(Error* err, Value* lval, Value* rval, Value* op)
|
static Value* parse_binary_expr(Error* err, Value* lval, Value* rval, Value* op)
|
||||||
{
|
{
|
||||||
if ((lval->type != VALUE_INTEGER && lval->type != VALUE_FLOATING)
|
if ((lval->type != VALUE_INTEGER && lval->type != VALUE_FLOATING)
|
||||||
|| (rval->type != VALUE_INTEGER && rval->type != VALUE_FLOATING)
|
|| (rval->type != VALUE_INTEGER && rval->type != VALUE_FLOATING)
|
||||||
|| op->type != VALUE_OPERATOR)
|
|| op->type != VALUE_OPERATOR)
|
||||||
{
|
{
|
||||||
error_push(err, "%s: unexpected token types: %s %s %s",
|
error_push(err, "%s: unexpected token types: %s %s %s",
|
||||||
__func__, value_type_str[lval->type],
|
__func__, value_type_str[lval->type],
|
||||||
value_type_str[rval->type], value_type_str[op->type]);
|
value_type_str[rval->type], value_type_str[op->type]);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lval->type == VALUE_FLOATING && rval->type == VALUE_INTEGER) {
|
if (lval->type == VALUE_FLOATING && rval->type == VALUE_INTEGER) {
|
||||||
conv_int_to_float(err, rval);
|
conv_int_to_float(err, rval);
|
||||||
} else if (lval->type == VALUE_INTEGER && rval->type == VALUE_FLOATING) {
|
} else if (lval->type == VALUE_INTEGER && rval->type == VALUE_FLOATING) {
|
||||||
conv_int_to_float(err, lval);
|
conv_int_to_float(err, lval);
|
||||||
}
|
}
|
||||||
if (!error_empty(err)) {
|
if (!error_empty(err)) {
|
||||||
error_push(err, "binary expression failed");
|
error_push(err, "binary expression failed");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value* result = malloc(sizeof *result);
|
Value* result = calloc(1, sizeof *result);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
error_push(err, "%s: failed to allocate value: %s", __func__, strerror(errno));
|
error_push(err, "%s: failed to allocate value: %s", __func__, strerror(errno));
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(stderr, "\ndoing op: %s %c %s", value_type_str[lval->type], op->op[0], value_type_str[rval->type]);
|
fprintf(stderr, "\ndoing op: %s %c %s", value_type_str[lval->type], op->op[0], value_type_str[rval->type]);
|
||||||
if (rval->type == VALUE_INTEGER && lval->type == VALUE_INTEGER) {
|
if (rval->type == VALUE_INTEGER && lval->type == VALUE_INTEGER) {
|
||||||
result->type = VALUE_INTEGER;
|
result->type = VALUE_INTEGER;
|
||||||
switch (op->op[0]) {
|
switch (op->op[0]) {
|
||||||
case '+':
|
case '+':
|
||||||
result->i64 = lval->i64 + rval->i64;
|
result->i64 = lval->i64 + rval->i64;
|
||||||
break;
|
break;
|
||||||
case '*':
|
case '*':
|
||||||
result->i64 = lval->i64 * rval->i64;
|
result->i64 = lval->i64 * rval->i64;
|
||||||
break;
|
break;
|
||||||
case '-':
|
case '-':
|
||||||
result->i64 = lval->i64 - rval->i64;
|
result->i64 = lval->i64 - rval->i64;
|
||||||
break;
|
break;
|
||||||
case '/':
|
case '/':
|
||||||
result->i64 = lval->i64 / rval->i64;
|
result->i64 = lval->i64 / rval->i64;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
printf("\nCALCULATED EXPRESSION %ld %c %ld = %ld\n", lval->i64,
|
printf("\nCALCULATED EXPRESSION %ld %c %ld = %ld\n", lval->i64,
|
||||||
op->op[0], rval->i64, result->i64);
|
op->op[0], rval->i64, result->i64);
|
||||||
} else if (rval->type == VALUE_FLOATING && lval->type == VALUE_FLOATING) {
|
} else if (rval->type == VALUE_FLOATING && lval->type == VALUE_FLOATING) {
|
||||||
result->type = VALUE_FLOATING;
|
result->type = VALUE_FLOATING;
|
||||||
switch (op->op[0]) {
|
switch (op->op[0]) {
|
||||||
case '+':
|
case '+':
|
||||||
result->f64 = lval->f64 + rval->f64;
|
result->f64 = lval->f64 + rval->f64;
|
||||||
break;
|
break;
|
||||||
case '*':
|
case '*':
|
||||||
result->f64 = lval->f64 * rval->f64;
|
result->f64 = lval->f64 * rval->f64;
|
||||||
break;
|
break;
|
||||||
case '-':
|
case '-':
|
||||||
result->f64 = lval->f64 - rval->f64;
|
result->f64 = lval->f64 - rval->f64;
|
||||||
break;
|
break;
|
||||||
case '/':
|
case '/':
|
||||||
result->f64 = lval->f64 / rval->f64;
|
result->f64 = lval->f64 / rval->f64;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
printf("\nCALCULATED EXPRESSION %lf %c %lf = %lf\n", lval->f64,
|
printf("\nCALCULATED EXPRESSION %lf %c %lf = %lf\n", lval->f64,
|
||||||
op->op[0], rval->f64, result->f64);
|
op->op[0], rval->f64, result->f64);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value* parser_handle_binary_expr(Error* err, Token* peek, Mfile* m)
|
static Value* parse_expr(Error* err, Parser* p)
|
||||||
{
|
{
|
||||||
Value* lval = parse_number(err, peek);
|
Value* lval = parse_number(err, p->cur);
|
||||||
if (!error_empty(err) || !lval) {
|
if (!error_empty(err) || !lval) {
|
||||||
goto generic_error;
|
goto generic_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
Token* op_tok = token_read(err, m);
|
if (!error_empty(err)) {
|
||||||
if (!error_empty(err)) {
|
goto generic_error;
|
||||||
goto generic_error;
|
} else if (p->next->type != TOKEN_OPERATOR) {
|
||||||
} else if (op_tok->type != TOKEN_OPERATOR) {
|
parser_advance(err, p);
|
||||||
goto syntax_error;
|
if (!error_empty(err)) {
|
||||||
}
|
goto syntax_error;
|
||||||
|
}
|
||||||
|
return lval;
|
||||||
|
}
|
||||||
|
|
||||||
Value* op = parse_operator(err, op_tok);
|
Value* op = parse_operator(err, p->next);
|
||||||
if (!error_empty(err)) {
|
if (!error_empty(err)) {
|
||||||
goto generic_error;
|
goto generic_error;
|
||||||
}
|
}
|
||||||
|
parser_advance(err, p);
|
||||||
|
if (!error_empty(err)) {
|
||||||
|
goto generic_error;
|
||||||
|
}
|
||||||
|
parser_advance(err, p);
|
||||||
|
if (!error_empty(err)) {
|
||||||
|
goto generic_error;
|
||||||
|
}
|
||||||
|
|
||||||
Token* rval_tok = token_read(err, m);
|
if (!error_empty(err)) {
|
||||||
if (!error_empty(err)) {
|
goto generic_error;
|
||||||
goto generic_error;
|
} else if (p->cur->type != TOKEN_INTEGER
|
||||||
} else if (rval_tok->type != TOKEN_INTEGER && rval_tok->type != TOKEN_FLOATING) {
|
&& p->cur->type != TOKEN_FLOATING)
|
||||||
goto syntax_error;
|
{
|
||||||
}
|
goto syntax_error;
|
||||||
Value* rval = parse_number(err, rval_tok);
|
}
|
||||||
if (!error_empty(err)) {
|
Value* rval = parse_expr(err, p);
|
||||||
goto generic_error;
|
if (!error_empty(err)) {
|
||||||
}
|
goto generic_error;
|
||||||
|
}
|
||||||
|
|
||||||
Value* result = parse_binary_expr(err, lval, rval, op);
|
Value* result = parse_binary_expr(err, lval, rval, op);
|
||||||
if (!error_empty(err)) {
|
if (!error_empty(err)) {
|
||||||
goto generic_error;
|
goto generic_error;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
syntax_error:
|
syntax_error:
|
||||||
error_push(err, "%s: syntax error, expected binary expression", __func__);
|
error_push(err, "%s: syntax error, expected binary expression", __func__);
|
||||||
generic_error:
|
generic_error:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value* parser_next(Error* err, Mfile* m)
|
static Value* parser_next(Error* err, Parser* p)
|
||||||
{
|
{
|
||||||
mfile_skip(m, isspace);
|
if (p->cur->type == EOF || !error_empty(err)) {
|
||||||
Token* t = token_read(err, m);
|
return NULL;
|
||||||
if (t->type == EOF) {
|
}
|
||||||
return NULL;
|
|
||||||
} else if (!error_empty(err)) {
|
Value* result;
|
||||||
return NULL;
|
switch (p->cur->type) {
|
||||||
}
|
case TOKEN_INTEGER:
|
||||||
Value* result;
|
case TOKEN_FLOATING:
|
||||||
switch (t->type) {
|
{
|
||||||
case TOKEN_INTEGER:
|
result = parse_expr(err, p);
|
||||||
case TOKEN_FLOATING:
|
if (!error_empty(err)) {
|
||||||
{
|
goto syntax_error;
|
||||||
result = parser_handle_binary_expr(err, t, m);
|
}
|
||||||
if (!error_empty(err)) {
|
break;
|
||||||
goto syntax_error;
|
}
|
||||||
}
|
default:
|
||||||
break;
|
syntax_error:
|
||||||
}
|
{
|
||||||
default: syntax_error: {
|
error_push(err, "(%s) syntax error: unexpected token %s", __func__, token_type_str[p->cur->type]);
|
||||||
error_push(err, "(%s) syntax error: unexpected token %s", __func__, token_type_str[t->type]);
|
return NULL;
|
||||||
return NULL;
|
}
|
||||||
}
|
}
|
||||||
}
|
return result;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========================================================================= */
|
/* ========================================================================= */
|
||||||
@@ -298,15 +325,15 @@ int main(int argc, char** argv)
|
|||||||
{
|
{
|
||||||
int status = EXIT_SUCCESS;
|
int status = EXIT_SUCCESS;
|
||||||
|
|
||||||
if (argc != 2) {
|
if (argc != 2) {
|
||||||
fprintf(stderr, "usage: %s <file>\n", argv[0]);
|
fprintf(stderr, "usage: %s <file>\n", argv[0]);
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a protected page for debugging purposes
|
// create a protected page for debugging purposes
|
||||||
size_t pagesize = sysconf(_SC_PAGESIZE);
|
size_t pagesize = sysconf(_SC_PAGESIZE);
|
||||||
void* protected_page = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
|
void* protected_page = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
|
||||||
mfile_overflow_slope = protected_page + 0;
|
mfile_overflow_slope = protected_page + 0;
|
||||||
mprotect(protected_page, pagesize, PROT_NONE);
|
mprotect(protected_page, pagesize, PROT_NONE);
|
||||||
|
|
||||||
Error err = ERROR_INIT;
|
Error err = ERROR_INIT;
|
||||||
@@ -317,13 +344,21 @@ int main(int argc, char** argv)
|
|||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!mfile_eof(m)) {
|
Parser p = {
|
||||||
parser_next(&err, m);
|
.cur = NULL,
|
||||||
if (!error_empty(&err)) {
|
.next = NULL,
|
||||||
error_print(&err);
|
.m = m,
|
||||||
return EXIT_FAILURE;
|
};
|
||||||
}
|
parser_advance(&err, &p);
|
||||||
}
|
parser_advance(&err, &p);
|
||||||
|
|
||||||
|
while (!mfile_eof(m)) {
|
||||||
|
parser_next(&err, &p);
|
||||||
|
if (!error_empty(&err)) {
|
||||||
|
error_print(&err);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
mfile_close(&err, m);
|
mfile_close(&err, m);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
1+2
|
1+2
|
||||||
1 + 4
|
1 + 4 + 3
|
||||||
5 / 2
|
5 / 2
|
||||||
1.3 * 2
|
1.3 * 2
|
||||||
|
|||||||
Reference in New Issue
Block a user