new -> operator
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
|
||||||
|
fun sayHello(self, other)
|
||||||
|
print("Hello ", other, " from ", self.name);
|
||||||
|
|
||||||
|
person = {
|
||||||
|
name: "Francesco",
|
||||||
|
sayHello: sayHello
|
||||||
|
};
|
||||||
|
|
||||||
|
person->sayHello("Giovanni");
|
||||||
|
|
||||||
+29
-24
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
scan = import("scan.noja");
|
scan = import("scan.noja");
|
||||||
|
|
||||||
newScanner = scan.newScanner;
|
newScanner = scan.newScanner;
|
||||||
@@ -7,14 +8,16 @@ consume = scan.consume;
|
|||||||
current = scan.current;
|
current = scan.current;
|
||||||
isDigit = scan.isDigit;
|
isDigit = scan.isDigit;
|
||||||
consumeSpaces = scan.consumeSpaces;
|
consumeSpaces = scan.consumeSpaces;
|
||||||
|
ord = string.ord;
|
||||||
|
cat = string.cat;
|
||||||
|
|
||||||
fun isAlpha(char: String)
|
fun isAlpha(char: String)
|
||||||
return (string.ord(char) >= string.ord('a') and string.ord(char) <= string.ord('z'))
|
return (ord(char) >= ord('a') and ord(char) <= ord('z'))
|
||||||
or (string.ord(char) >= string.ord('A') and string.ord(char) <= string.ord('Z'));
|
or (ord(char) >= ord('A') and ord(char) <= ord('Z'));
|
||||||
|
|
||||||
fun isUpper(char: String)
|
fun isUpper(char: String)
|
||||||
return string.ord(char) >= string.ord('A')
|
return ord(char) >= ord('A')
|
||||||
and string.ord(char) <= string.ord('Z');
|
and ord(char) <= ord('Z');
|
||||||
|
|
||||||
fun isUnreserved(char: String)
|
fun isUnreserved(char: String)
|
||||||
return isAlpha(char)
|
return isAlpha(char)
|
||||||
@@ -46,7 +49,7 @@ fun parseMethod(scan: Scanner) {
|
|||||||
|
|
||||||
method = "";
|
method = "";
|
||||||
do {
|
do {
|
||||||
method = string.cat(method, char);
|
method = cat(method, char);
|
||||||
consume(scan);
|
consume(scan);
|
||||||
char = current(scan);
|
char = current(scan);
|
||||||
} while char != none and isUpper(char);
|
} while char != none and isUpper(char);
|
||||||
@@ -65,8 +68,8 @@ fun parsePort(scan: Scanner) {
|
|||||||
|
|
||||||
port = 0;
|
port = 0;
|
||||||
do {
|
do {
|
||||||
digit = string.ord(char)
|
digit = ord(char)
|
||||||
- string.ord("0");
|
- ord("0");
|
||||||
port = port * 10 + digit;
|
port = port * 10 + digit;
|
||||||
consume(scan);
|
consume(scan);
|
||||||
char = current(scan);
|
char = current(scan);
|
||||||
@@ -90,7 +93,7 @@ fun parseHost(scan: Scanner) {
|
|||||||
|
|
||||||
name = "";
|
name = "";
|
||||||
do {
|
do {
|
||||||
name = string.cat(name, char);
|
name = cat(name, char);
|
||||||
consume(scan);
|
consume(scan);
|
||||||
char = current(scan);
|
char = current(scan);
|
||||||
} while char != none and isHostname(char);
|
} while char != none and isHostname(char);
|
||||||
@@ -104,7 +107,7 @@ fun parsePath(scan: Scanner) {
|
|||||||
path = "";
|
path = "";
|
||||||
char = current(scan);
|
char = current(scan);
|
||||||
if char != none and char == "/": {
|
if char != none and char == "/": {
|
||||||
path = string.cat(path, char);
|
path = cat(path, char);
|
||||||
consume(scan);
|
consume(scan);
|
||||||
char = current(scan);
|
char = current(scan);
|
||||||
} else {
|
} else {
|
||||||
@@ -114,13 +117,13 @@ fun parsePath(scan: Scanner) {
|
|||||||
|
|
||||||
while char != none and isPChar(char): {
|
while char != none and isPChar(char): {
|
||||||
do {
|
do {
|
||||||
path = string.cat(path, char);
|
path = cat(path, char);
|
||||||
consume(scan);
|
consume(scan);
|
||||||
char = current(scan);
|
char = current(scan);
|
||||||
} while char != none and isPChar(char);
|
} while char != none and isPChar(char);
|
||||||
if char == none or char != "/":
|
if char == none or char != "/":
|
||||||
break;
|
break;
|
||||||
path = string.cat(path, char);
|
path = cat(path, char);
|
||||||
consume(scan);
|
consume(scan);
|
||||||
char = current(scan);
|
char = current(scan);
|
||||||
}
|
}
|
||||||
@@ -143,7 +146,7 @@ fun parseQuery(scan: Scanner) {
|
|||||||
consume(scan); # Consume the "?"
|
consume(scan); # Consume the "?"
|
||||||
query = "";
|
query = "";
|
||||||
while isQuery(char = current(scan)): {
|
while isQuery(char = current(scan)): {
|
||||||
query = string.cat(query, char);
|
query = cat(query, char);
|
||||||
consume(scan);
|
consume(scan);
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
@@ -165,7 +168,7 @@ fun parseFragment(scan: Scanner) {
|
|||||||
consume(scan); # Consume the "#"
|
consume(scan); # Consume the "#"
|
||||||
fragment = "";
|
fragment = "";
|
||||||
while isFragment(char = current(scan)): {
|
while isFragment(char = current(scan)): {
|
||||||
fragment = string.cat(fragment, char);
|
fragment = cat(fragment, char);
|
||||||
consume(scan);
|
consume(scan);
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
@@ -174,12 +177,15 @@ fun parseFragment(scan: Scanner) {
|
|||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
Host = { name: String, port: ?int };
|
Host = {
|
||||||
|
name: String,
|
||||||
|
port: ?int
|
||||||
|
};
|
||||||
|
|
||||||
URL = {
|
URL = {
|
||||||
schema: ?String,
|
schema: ?String,
|
||||||
host: ?Host,
|
host: ?Host,
|
||||||
path: ?String,
|
path: String,
|
||||||
query: ?String,
|
query: ?String,
|
||||||
fragment: ?String
|
fragment: ?String
|
||||||
};
|
};
|
||||||
@@ -204,7 +210,7 @@ fun parseSchema(scan: Scanner) {
|
|||||||
start = scan.i;
|
start = scan.i;
|
||||||
schema = "";
|
schema = "";
|
||||||
do {
|
do {
|
||||||
schema = string.cat(schema, char);
|
schema = cat(schema, char);
|
||||||
consume(scan);
|
consume(scan);
|
||||||
char = current(scan);
|
char = current(scan);
|
||||||
} while isSchema(char);
|
} while isSchema(char);
|
||||||
@@ -253,7 +259,6 @@ fun parseURL(scan: Scanner) {
|
|||||||
|
|
||||||
query = parseQuery(scan);
|
query = parseQuery(scan);
|
||||||
fragment = parseFragment(scan);
|
fragment = parseFragment(scan);
|
||||||
print(scan);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
host : host,
|
host : host,
|
||||||
@@ -272,13 +277,13 @@ fun parseVersion(scan: Scanner) {
|
|||||||
return none, "Invalid version token";
|
return none, "Invalid version token";
|
||||||
|
|
||||||
consume(scan, 5);
|
consume(scan, 5);
|
||||||
major = string.ord(current(scan))
|
major = ord(current(scan))
|
||||||
- string.ord("0");
|
- ord("0");
|
||||||
|
|
||||||
if hint(scan, 1) != "." and isDigit(hint(scan, 2)): {
|
if hint(scan, 1) != "." and isDigit(hint(scan, 2)): {
|
||||||
consume(scan, 2);
|
consume(scan, 2);
|
||||||
minor = string.ord(current(scan))
|
minor = ord(current(scan))
|
||||||
- string.ord("0");
|
- ord("0");
|
||||||
} else
|
} else
|
||||||
minor = 0;
|
minor = 0;
|
||||||
consume(scan);
|
consume(scan);
|
||||||
@@ -343,7 +348,7 @@ fun parse(src: String) {
|
|||||||
# Header name until the next ":"
|
# Header name until the next ":"
|
||||||
name = "";
|
name = "";
|
||||||
do {
|
do {
|
||||||
name = string.cat(name, char);
|
name = cat(name, char);
|
||||||
consume(scan);
|
consume(scan);
|
||||||
char = current(scan);
|
char = current(scan);
|
||||||
} while char != ":" and char != none;
|
} while char != ":" and char != none;
|
||||||
@@ -357,7 +362,7 @@ fun parse(src: String) {
|
|||||||
# Header body until \r
|
# Header body until \r
|
||||||
body = "";
|
body = "";
|
||||||
do {
|
do {
|
||||||
body = string.cat(body, char);
|
body = cat(body, char);
|
||||||
consume(scan);
|
consume(scan);
|
||||||
char = current(scan);
|
char = current(scan);
|
||||||
} while char != "\r" and char != none;
|
} while char != "\r" and char != none;
|
||||||
@@ -371,7 +376,7 @@ fun parse(src: String) {
|
|||||||
return newRequest(method, url, version, headers);
|
return newRequest(method, url, version, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
sample = string.cat(
|
sample = cat(
|
||||||
"GET /search?client=firefox-b-d&q=http+request+dataset HTTP/2\r\n",
|
"GET /search?client=firefox-b-d&q=http+request+dataset HTTP/2\r\n",
|
||||||
"Host: www.google.com\r\n",
|
"Host: www.google.com\r\n",
|
||||||
"user-agent: curl/7.84.0\r\n",
|
"user-agent: curl/7.84.0\r\n",
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ static const InstrInfo instr_table[] = {
|
|||||||
INSTR(CHECKTYPE, OPTP_INT, OPTP_STRING)
|
INSTR(CHECKTYPE, OPTP_INT, OPTP_STRING)
|
||||||
INSTR(CALL, OPTP_INT, OPTP_INT)
|
INSTR(CALL, OPTP_INT, OPTP_INT)
|
||||||
INSTR(SELECT)
|
INSTR(SELECT)
|
||||||
|
INSTR(SELECT2)
|
||||||
INSTR(INSERT)
|
INSTR(INSERT)
|
||||||
INSTR(INSERT2)
|
INSTR(INSERT2)
|
||||||
INSTR(PUSHINT, OPTP_INT)
|
INSTR(PUSHINT, OPTP_INT)
|
||||||
@@ -520,6 +521,13 @@ _Bool ExeBuilder_Append(ExeBuilder *exeb, Error *error, Opcode opcode, Operand *
|
|||||||
|
|
||||||
const InstrInfo *info = Executable_GetInstrByOpcode(opcode);
|
const InstrInfo *info = Executable_GetInstrByOpcode(opcode);
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
if (info == NULL) {
|
||||||
|
Error_Report(error, 1, "Missing instruction table entry for opcode");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if(opc != info->opcount)
|
if(opc != info->opcount)
|
||||||
{
|
{
|
||||||
// ERROR: Too many operands were provided.
|
// ERROR: Too many operands were provided.
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ typedef enum {
|
|||||||
OPCODE_POP,
|
OPCODE_POP,
|
||||||
OPCODE_CALL,
|
OPCODE_CALL,
|
||||||
OPCODE_SELECT,
|
OPCODE_SELECT,
|
||||||
|
OPCODE_SELECT2,
|
||||||
OPCODE_INSERT,
|
OPCODE_INSERT,
|
||||||
OPCODE_INSERT2,
|
OPCODE_INSERT2,
|
||||||
OPCODE_PUSHINT,
|
OPCODE_PUSHINT,
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ typedef enum {
|
|||||||
EXPR_OR,
|
EXPR_OR,
|
||||||
EXPR_NOT,
|
EXPR_NOT,
|
||||||
|
|
||||||
|
EXPR_ARW,
|
||||||
EXPR_ASS,
|
EXPR_ASS,
|
||||||
EXPR_INT,
|
EXPR_INT,
|
||||||
EXPR_MAP,
|
EXPR_MAP,
|
||||||
|
|||||||
@@ -198,19 +198,34 @@ static void emitInstrForFuncCallNode(CodegenContext *ctx, CallExprNode *expr,
|
|||||||
Label *label_break, int returns)
|
Label *label_break, int returns)
|
||||||
{
|
{
|
||||||
Node *arg = expr->argv;
|
Node *arg = expr->argv;
|
||||||
|
|
||||||
while(arg)
|
while(arg)
|
||||||
{
|
{
|
||||||
emitInstrForNode(ctx, arg, label_break);
|
emitInstrForNode(ctx, arg, label_break);
|
||||||
arg = arg->next;
|
arg = arg->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int argc;
|
||||||
|
if (expr->func->kind == NODE_EXPR && ((ExprNode*) expr->func)->kind == EXPR_ARW) {
|
||||||
|
IndexSelectionExprNode *selection = (IndexSelectionExprNode*) expr->func;
|
||||||
|
Node *idx = selection->idx;
|
||||||
|
Node *set = selection->set;
|
||||||
|
emitInstrForNode(ctx, set, label_break);
|
||||||
|
emitInstrForNode(ctx, idx, label_break);
|
||||||
|
CodegenContext_EmitInstr(ctx, OPCODE_SELECT2, NULL, 0,
|
||||||
|
expr->func->offset,
|
||||||
|
expr->func->length);
|
||||||
|
argc = expr->argc+1;
|
||||||
|
} else {
|
||||||
emitInstrForNode(ctx, expr->func, label_break);
|
emitInstrForNode(ctx, expr->func, label_break);
|
||||||
|
argc = expr->argc;
|
||||||
|
}
|
||||||
|
|
||||||
Operand ops[2];
|
Operand ops[2];
|
||||||
ops[0] = (Operand) { .type = OPTP_INT, .as_int = expr->argc };
|
ops[0] = (Operand) { .type = OPTP_INT, .as_int = argc };
|
||||||
ops[1] = (Operand) { .type = OPTP_INT, .as_int = returns };
|
ops[1] = (Operand) { .type = OPTP_INT, .as_int = returns };
|
||||||
CodegenContext_EmitInstr(ctx, OPCODE_CALL, ops, 2, expr->base.base.offset, expr->base.base.length);
|
CodegenContext_EmitInstr(ctx, OPCODE_CALL, ops, 2,
|
||||||
|
expr->base.base.offset,
|
||||||
|
expr->base.base.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void emitInstrForExprNode(CodegenContext *ctx, ExprNode *expr,
|
static void emitInstrForExprNode(CodegenContext *ctx, ExprNode *expr,
|
||||||
@@ -394,6 +409,11 @@ static void emitInstrForExprNode(CodegenContext *ctx, ExprNode *expr,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case EXPR_ARW:
|
||||||
|
CodegenContext_ReportErrorAndJump(ctx, 0, "Operator -> out of a function call");
|
||||||
|
UNREACHABLE;
|
||||||
|
return;
|
||||||
|
|
||||||
case EXPR_AND:
|
case EXPR_AND:
|
||||||
{
|
{
|
||||||
OperExprNode *oper = (OperExprNode*) expr;
|
OperExprNode *oper = (OperExprNode*) expr;
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
** +--------------------------------------------------------------------------+
|
** +--------------------------------------------------------------------------+
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@@ -112,6 +113,7 @@ typedef enum {
|
|||||||
TNQL,
|
TNQL,
|
||||||
TLEQ,
|
TLEQ,
|
||||||
TGEQ,
|
TGEQ,
|
||||||
|
TARW,
|
||||||
|
|
||||||
} TokenKind;
|
} TokenKind;
|
||||||
|
|
||||||
@@ -331,6 +333,7 @@ static Token *tokenize(Source *src, BPAlloc *alloc, Error *error)
|
|||||||
{ TCOMMA, 1, "," },
|
{ TCOMMA, 1, "," },
|
||||||
{ TSMT, 1, "|" },
|
{ TSMT, 1, "|" },
|
||||||
{ TMOD, 1, "%" },
|
{ TMOD, 1, "%" },
|
||||||
|
{ TARW, 2, "->" },
|
||||||
};
|
};
|
||||||
|
|
||||||
_Bool found = 0;
|
_Bool found = 0;
|
||||||
@@ -1364,7 +1367,7 @@ static Node *parse_primary_expresion(Context *ctx)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Node *makeIndexSelectionExprNode(Context *ctx, Node *set, Node *idx)
|
static Node *makeIndexOrArrowSelectionExprNode(Context *ctx, bool arrow, Node *set, Node *idx)
|
||||||
{
|
{
|
||||||
IndexSelectionExprNode *sel = BPAlloc_Malloc(ctx->alloc, sizeof(IndexSelectionExprNode));
|
IndexSelectionExprNode *sel = BPAlloc_Malloc(ctx->alloc, sizeof(IndexSelectionExprNode));
|
||||||
|
|
||||||
@@ -1378,7 +1381,7 @@ static Node *makeIndexSelectionExprNode(Context *ctx, Node *set, Node *idx)
|
|||||||
sel->base.base.next = NULL;
|
sel->base.base.next = NULL;
|
||||||
sel->base.base.offset = -1;
|
sel->base.base.offset = -1;
|
||||||
sel->base.base.length = -1;
|
sel->base.base.length = -1;
|
||||||
sel->base.kind = EXPR_SELECT;
|
sel->base.kind = arrow ? EXPR_ARW : EXPR_SELECT;
|
||||||
sel->set = set;
|
sel->set = set;
|
||||||
sel->idx = idx;
|
sel->idx = idx;
|
||||||
return (Node*) sel;
|
return (Node*) sel;
|
||||||
@@ -1398,20 +1401,23 @@ static Node *parse_postfix_expression(Context *ctx)
|
|||||||
switch(current(ctx))
|
switch(current(ctx))
|
||||||
{
|
{
|
||||||
case '.':
|
case '.':
|
||||||
|
case TARW:
|
||||||
{
|
{
|
||||||
|
bool arrow = (current(ctx) == TARW);
|
||||||
|
|
||||||
next(ctx);
|
next(ctx);
|
||||||
|
|
||||||
// We expect an identifier after the dot.
|
// We expect an identifier after the dot or arrow
|
||||||
|
|
||||||
if(done(ctx))
|
if(done(ctx))
|
||||||
{
|
{
|
||||||
Error_Report(ctx->error, 0, "Source ended after dot of dot selection expression");
|
Error_Report(ctx->error, 0, "Source ended after dot or arrow");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(current(ctx) != TIDENT)
|
if(current(ctx) != TIDENT)
|
||||||
{
|
{
|
||||||
Error_Report(ctx->error, 0, "Got unexpected token \"%.*s\" after fot of dot selection expression where an identifier was expected", ctx->token->length, ctx->src + ctx->token->offset);
|
Error_Report(ctx->error, 0, "Got unexpected token \"%.*s\" after dot or arrow, where an identifier was expected", ctx->token->length, ctx->src + ctx->token->offset);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1419,7 +1425,7 @@ static Node *parse_postfix_expression(Context *ctx)
|
|||||||
if(idx == NULL)
|
if(idx == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
Node *sel = makeIndexSelectionExprNode(ctx, node, idx);
|
Node *sel = makeIndexOrArrowSelectionExprNode(ctx, arrow, node, idx);
|
||||||
if(sel == NULL)
|
if(sel == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
@@ -1446,7 +1452,7 @@ static Node *parse_postfix_expression(Context *ctx)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
Node *sel = makeIndexSelectionExprNode(ctx, node, ls->itemc == 1 ? ls->items : (Node*) ls);
|
Node *sel = makeIndexOrArrowSelectionExprNode(ctx, false, node, ls->itemc == 1 ? ls->items : (Node*) ls);
|
||||||
if(sel == NULL)
|
if(sel == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
@@ -1723,7 +1729,6 @@ static Node *parse_expression_2(Context *ctx, Node *left_expr, int min_prec, _Bo
|
|||||||
case '=': temp->base.kind = EXPR_ASS; break;
|
case '=': temp->base.kind = EXPR_ASS; break;
|
||||||
case '|': temp->base.kind = EXPR_SUMTYPE; break;
|
case '|': temp->base.kind = EXPR_SUMTYPE; break;
|
||||||
case ',': temp->base.kind = EXPR_PAIR; break;
|
case ',': temp->base.kind = EXPR_PAIR; break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
UNREACHABLE;
|
UNREACHABLE;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -978,12 +978,18 @@ static _Bool step(Runtime *runtime, Error *error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case OPCODE_SELECT:
|
case OPCODE_SELECT:
|
||||||
|
case OPCODE_SELECT2:
|
||||||
{
|
{
|
||||||
ASSERT(opc == 0);
|
ASSERT(opc == 0);
|
||||||
|
|
||||||
if(runtime->frame->used < 2)
|
int to_be_popped = (opcode == OPCODE_SELECT) ? 2 : 1;
|
||||||
|
|
||||||
|
if(runtime->frame->used < to_be_popped)
|
||||||
{
|
{
|
||||||
Error_Report(error, 1, "Frame has not enough values on the stack to run SELECT instruction");
|
const char *name = "SELECT";
|
||||||
|
if (opcode == OPCODE_SELECT2)
|
||||||
|
name = "SELECT2";
|
||||||
|
Error_Report(error, 1, "Frame has not enough values on the stack to run %s instruction", name);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -992,7 +998,7 @@ static _Bool step(Runtime *runtime, Error *error)
|
|||||||
|
|
||||||
ASSERT(col != NULL && key != NULL);
|
ASSERT(col != NULL && key != NULL);
|
||||||
|
|
||||||
if(!Runtime_Pop(runtime, error, 2))
|
if(!Runtime_Pop(runtime, error, to_be_popped))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
ASSERT(error->occurred == 0);
|
ASSERT(error->occurred == 0);
|
||||||
@@ -1007,7 +1013,6 @@ static _Bool step(Runtime *runtime, Error *error)
|
|||||||
Error_Free(&dummy);
|
Error_Free(&dummy);
|
||||||
|
|
||||||
val = Object_NewNone(runtime->heap, error);
|
val = Object_NewNone(runtime->heap, error);
|
||||||
|
|
||||||
if(val == NULL)
|
if(val == NULL)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user