Implement defer
This commit is contained in:
parent
077de53c74
commit
39f3337c69
23
boot/ast.cc
23
boot/ast.cc
@ -90,6 +90,14 @@ namespace boot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void empty_visitor::visit(defer_statement *defer)
|
||||||
|
{
|
||||||
|
for (statement *const body_statement : defer->statements)
|
||||||
|
{
|
||||||
|
body_statement->accept(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void empty_visitor::visit(block *block)
|
void empty_visitor::visit(block *block)
|
||||||
{
|
{
|
||||||
for (constant_definition *const constant : block->constants)
|
for (constant_definition *const constant : block->constants)
|
||||||
@ -204,7 +212,7 @@ namespace boot
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void empty_visitor::visit(string_literal *)
|
void empty_visitor::visit(number_literal<std::string> *)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,19 +543,22 @@ namespace boot
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
string_literal::string_literal(const struct position position, const std::string& value)
|
defer_statement::defer_statement(const struct position position)
|
||||||
: literal(position), m_string(value)
|
: statement(position)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void string_literal::accept(parser_visitor *visitor)
|
void defer_statement::accept(parser_visitor *visitor)
|
||||||
{
|
{
|
||||||
visitor->visit(this);
|
visitor->visit(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& string_literal::string() const
|
defer_statement::~defer_statement()
|
||||||
{
|
{
|
||||||
return m_string;
|
for (statement *body_statement : statements)
|
||||||
|
{
|
||||||
|
delete body_statement;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
designator_expression::designator_expression(const struct position position)
|
designator_expression::designator_expression(const struct position position)
|
||||||
|
@ -41,36 +41,36 @@ namespace boot
|
|||||||
return m_errors;
|
return m_errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<char> escape_char(char escape)
|
char escape_char(char escape)
|
||||||
{
|
{
|
||||||
switch (escape)
|
switch (escape)
|
||||||
{
|
{
|
||||||
case 'n':
|
case 'n':
|
||||||
return std::make_optional<char>('\n');
|
return '\n';
|
||||||
case 'a':
|
case 'a':
|
||||||
return std::make_optional<char>('\a');
|
return '\a';
|
||||||
case 'b':
|
case 'b':
|
||||||
return std::make_optional<char>('\b');
|
return '\b';
|
||||||
case 't':
|
case 't':
|
||||||
return std::make_optional<char>('\t');
|
return '\t';
|
||||||
case 'f':
|
case 'f':
|
||||||
return std::make_optional<char>('\f');
|
return '\f';
|
||||||
case 'r':
|
case 'r':
|
||||||
return std::make_optional<char>('\r');
|
return '\r';
|
||||||
case 'v':
|
case 'v':
|
||||||
return std::make_optional<char>('\v');
|
return '\v';
|
||||||
case '\\':
|
case '\\':
|
||||||
return std::make_optional<char>('\\');
|
return '\\';
|
||||||
case '\'':
|
case '\'':
|
||||||
return std::make_optional<char>('\'');
|
return '\'';
|
||||||
case '"':
|
case '"':
|
||||||
return std::make_optional<char>('"');
|
return '"';
|
||||||
case '?':
|
case '?':
|
||||||
return std::make_optional<char>('\?');
|
return '\?';
|
||||||
case '0':
|
case '0':
|
||||||
return std::make_optional<char>('\0');
|
return '\0';
|
||||||
default:
|
default:
|
||||||
return std::nullopt;
|
return escape_invalid_char;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,6 +127,9 @@ as {
|
|||||||
sizeof {
|
sizeof {
|
||||||
return yy::parser::make_SIZEOF(this->location);
|
return yy::parser::make_SIZEOF(this->location);
|
||||||
}
|
}
|
||||||
|
defer {
|
||||||
|
return yy::parser::make_DEFER(this->location);
|
||||||
|
}
|
||||||
[A-Za-z_][A-Za-z0-9_]* {
|
[A-Za-z_][A-Za-z0-9_]* {
|
||||||
return yy::parser::make_IDENTIFIER(yytext, this->location);
|
return yy::parser::make_IDENTIFIER(yytext, this->location);
|
||||||
}
|
}
|
||||||
@ -155,15 +158,12 @@ sizeof {
|
|||||||
return yy::parser::make_CHARACTER(std::string(&character, 1), this->location);
|
return yy::parser::make_CHARACTER(std::string(&character, 1), this->location);
|
||||||
}
|
}
|
||||||
'\\[0nabtfrv\\'"?]' {
|
'\\[0nabtfrv\\'"?]' {
|
||||||
std::optional<char> escape = elna::boot::escape_char(yytext[2]);
|
char escape = elna::boot::escape_char(yytext[2]);
|
||||||
if (escape.has_value())
|
if (escape == escape_invalid_char)
|
||||||
{
|
|
||||||
return yy::parser::make_CHARACTER(std::string(&escape.value(), 1), this->location);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
REJECT;
|
REJECT;
|
||||||
}
|
}
|
||||||
|
return yy::parser::make_CHARACTER(std::string(&escape, 1), this->location);
|
||||||
}
|
}
|
||||||
\"[[:print:]]*\" {
|
\"[[:print:]]*\" {
|
||||||
std::string result;
|
std::string result;
|
||||||
@ -191,15 +191,12 @@ sizeof {
|
|||||||
{
|
{
|
||||||
++current_position;
|
++current_position;
|
||||||
|
|
||||||
std::optional<char> escape = elna::boot::escape_char(*current_position);
|
char escape = elna::boot::escape_char(*current_position);
|
||||||
if (escape.has_value())
|
if (escape == elna::boot::escape_invalid_char)
|
||||||
{
|
|
||||||
result.push_back(escape.value());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
REJECT;
|
REJECT;
|
||||||
}
|
}
|
||||||
|
result.push_back(escape);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
%token <bool> BOOLEAN
|
%token <bool> BOOLEAN
|
||||||
%token IF WHILE DO THEN ELSE ELSIF RETURN
|
%token IF WHILE DO THEN ELSE ELSIF RETURN
|
||||||
%token CONST VAR PROCEDURE ARRAY OF TYPE RECORD POINTER TO UNION
|
%token CONST VAR PROCEDURE ARRAY OF TYPE RECORD POINTER TO UNION
|
||||||
%token BEGIN_BLOCK END_BLOCK EXTERN
|
%token BEGIN_BLOCK END_BLOCK EXTERN DEFER
|
||||||
%token LEFT_PAREN RIGHT_PAREN LEFT_SQUARE RIGHT_SQUARE SEMICOLON DOT COMMA
|
%token LEFT_PAREN RIGHT_PAREN LEFT_SQUARE RIGHT_SQUARE SEMICOLON DOT COMMA
|
||||||
%token AND OR NOT CAST AS SIZEOF
|
%token AND OR NOT CAST AS SIZEOF
|
||||||
%token GREATER_EQUAL LESS_EQUAL LESS_THAN GREATER_THAN NOT_EQUAL EQUALS
|
%token GREATER_EQUAL LESS_EQUAL LESS_THAN GREATER_THAN NOT_EQUAL EQUALS
|
||||||
@ -109,6 +109,7 @@
|
|||||||
%type <std::vector<std::pair<std::string, elna::boot::type_expression *>>> field_list;
|
%type <std::vector<std::pair<std::string, elna::boot::type_expression *>>> field_list;
|
||||||
%type <std::vector<elna::boot::conditional_statements *>> elsif_statement_list;
|
%type <std::vector<elna::boot::conditional_statements *>> elsif_statement_list;
|
||||||
%type <elna::boot::cast_expression *> cast_expression;
|
%type <elna::boot::cast_expression *> cast_expression;
|
||||||
|
%type <elna::boot::defer_statement *> defer_statement;
|
||||||
%%
|
%%
|
||||||
program:
|
program:
|
||||||
constant_part type_part variable_part procedure_part BEGIN_BLOCK optional_statements END_BLOCK DOT
|
constant_part type_part variable_part procedure_part BEGIN_BLOCK optional_statements END_BLOCK DOT
|
||||||
@ -206,11 +207,15 @@ if_statement:
|
|||||||
$$ = new elna::boot::if_statement(elna::boot::make_position(@1), then, _else);
|
$$ = new elna::boot::if_statement(elna::boot::make_position(@1), then, _else);
|
||||||
std::swap($5, $$->branches);
|
std::swap($5, $$->branches);
|
||||||
}
|
}
|
||||||
return_statement:
|
return_statement: RETURN expression
|
||||||
RETURN expression
|
|
||||||
{
|
{
|
||||||
$$ = new elna::boot::return_statement(elna::boot::make_position(@1), $2);
|
$$ = new elna::boot::return_statement(elna::boot::make_position(@1), $2);
|
||||||
}
|
}
|
||||||
|
defer_statement: DEFER optional_statements END_BLOCK
|
||||||
|
{
|
||||||
|
$$ = new elna::boot::defer_statement(elna::boot::make_position(@1));
|
||||||
|
std::swap($2, $$->statements);
|
||||||
|
}
|
||||||
literal:
|
literal:
|
||||||
INTEGER
|
INTEGER
|
||||||
{
|
{
|
||||||
@ -238,7 +243,7 @@ literal:
|
|||||||
}
|
}
|
||||||
| STRING
|
| STRING
|
||||||
{
|
{
|
||||||
$$ = new elna::boot::string_literal(elna::boot::make_position(@1), $1);
|
$$ = new elna::boot::number_literal<std::string>(elna::boot::make_position(@1), $1);
|
||||||
}
|
}
|
||||||
operand:
|
operand:
|
||||||
literal { $$ = $1; }
|
literal { $$ = $1; }
|
||||||
@ -367,6 +372,7 @@ statement:
|
|||||||
{
|
{
|
||||||
$$ = new elna::boot::call_statement(elna::boot::make_position(@1), $1);
|
$$ = new elna::boot::call_statement(elna::boot::make_position(@1), $1);
|
||||||
}
|
}
|
||||||
|
| defer_statement { $$ = $1; }
|
||||||
statements:
|
statements:
|
||||||
statement SEMICOLON statements
|
statement SEMICOLON statements
|
||||||
{
|
{
|
||||||
|
@ -87,17 +87,18 @@ elna.stagefeedback: stagefeedback-start
|
|||||||
-mv elna/*$(objext) stagefeedback/elna
|
-mv elna/*$(objext) stagefeedback/elna
|
||||||
|
|
||||||
ELNA_INCLUDES = -I $(srcdir)/elna/include -I elna/generated
|
ELNA_INCLUDES = -I $(srcdir)/elna/include -I elna/generated
|
||||||
|
ELNA_CXXFLAGS = -std=c++14
|
||||||
|
|
||||||
elna/%.o: elna/boot/%.cc elna/generated/parser.hh elna/generated/location.hh
|
elna/%.o: elna/boot/%.cc elna/generated/parser.hh elna/generated/location.hh
|
||||||
$(COMPILE) $(ELNA_INCLUDES) $<
|
$(COMPILE) $(ELNA_CXXFLAGS) $(ELNA_INCLUDES) $<
|
||||||
$(POSTCOMPILE)
|
$(POSTCOMPILE)
|
||||||
|
|
||||||
elna/%.o: elna/generated/%.cc elna/generated/parser.hh elna/generated/location.hh
|
elna/%.o: elna/generated/%.cc elna/generated/parser.hh elna/generated/location.hh
|
||||||
$(COMPILE) $(ELNA_INCLUDES) $<
|
$(COMPILE) $(ELNA_CXXFLAGS) $(ELNA_INCLUDES) $<
|
||||||
$(POSTCOMPILE)
|
$(POSTCOMPILE)
|
||||||
|
|
||||||
elna/%.o: elna/gcc/%.cc elna/generated/parser.hh elna/generated/location.hh
|
elna/%.o: elna/gcc/%.cc elna/generated/parser.hh elna/generated/location.hh
|
||||||
$(COMPILE) $(ELNA_INCLUDES) $<
|
$(COMPILE) $(ELNA_CXXFLAGS) $(ELNA_INCLUDES) $<
|
||||||
$(POSTCOMPILE)
|
$(POSTCOMPILE)
|
||||||
|
|
||||||
elna/generated/parser.cc: elna/boot/parser.yy
|
elna/generated/parser.cc: elna/boot/parser.yy
|
||||||
|
@ -50,7 +50,7 @@ namespace gcc
|
|||||||
|
|
||||||
if (return_type == void_type_node)
|
if (return_type == void_type_node)
|
||||||
{
|
{
|
||||||
append_to_statement_list(stmt, &this->current_statements);
|
this->scope.front().append_statement(stmt);
|
||||||
this->current_expression = NULL_TREE;
|
this->current_expression = NULL_TREE;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -133,7 +133,7 @@ namespace gcc
|
|||||||
tree set_result = build2(INIT_EXPR, void_type_node, DECL_RESULT(main_fndecl),
|
tree set_result = build2(INIT_EXPR, void_type_node, DECL_RESULT(main_fndecl),
|
||||||
build_int_cst_type(integer_type_node, 0));
|
build_int_cst_type(integer_type_node, 0));
|
||||||
tree return_stmt = build1(RETURN_EXPR, void_type_node, set_result);
|
tree return_stmt = build1(RETURN_EXPR, void_type_node, set_result);
|
||||||
append_to_statement_list(return_stmt, &this->current_statements);
|
this->scope.front().append_statement(return_stmt);
|
||||||
tree_symbol_mapping mapping = leave_scope();
|
tree_symbol_mapping mapping = leave_scope();
|
||||||
|
|
||||||
BLOCK_SUPERCONTEXT(mapping.block()) = this->main_fndecl;
|
BLOCK_SUPERCONTEXT(mapping.block()) = this->main_fndecl;
|
||||||
@ -214,19 +214,29 @@ namespace gcc
|
|||||||
|
|
||||||
void generic_visitor::enter_scope()
|
void generic_visitor::enter_scope()
|
||||||
{
|
{
|
||||||
this->current_statements = alloc_stmt_list();
|
scope.emplace_front();
|
||||||
this->variable_chain = tree_chain();
|
|
||||||
this->symbol_map = std::make_shared<boot::symbol_table<tree>>(this->symbol_map);
|
this->symbol_map = std::make_shared<boot::symbol_table<tree>>(this->symbol_map);
|
||||||
}
|
}
|
||||||
|
|
||||||
tree_symbol_mapping generic_visitor::leave_scope()
|
tree_symbol_mapping generic_visitor::leave_scope()
|
||||||
{
|
{
|
||||||
tree new_block = build_block(variable_chain.head(),
|
tree new_block = build_block(this->scope.front().variables.head(),
|
||||||
NULL_TREE, NULL_TREE, NULL_TREE);
|
this->scope.front().blocks.head(), NULL_TREE, NULL_TREE);
|
||||||
tree bind_expr = build3(BIND_EXPR, void_type_node, variable_chain.head(),
|
|
||||||
this->current_statements, new_block);
|
for (tree it = this->scope.front().blocks.head(); it != NULL_TREE; it = BLOCK_CHAIN(it))
|
||||||
|
{
|
||||||
|
BLOCK_SUPERCONTEXT(it) = new_block;
|
||||||
|
}
|
||||||
|
tree bind_expr = build3(BIND_EXPR, void_type_node, this->scope.front().variables.head(),
|
||||||
|
this->scope.front().chain_defer(), new_block);
|
||||||
this->symbol_map = this->symbol_map->scope();
|
this->symbol_map = this->symbol_map->scope();
|
||||||
|
|
||||||
|
scope.pop_front();
|
||||||
|
|
||||||
|
if (!scope.empty())
|
||||||
|
{
|
||||||
|
scope.front().blocks.append(new_block);
|
||||||
|
}
|
||||||
return tree_symbol_mapping{ bind_expr, new_block };
|
return tree_symbol_mapping{ bind_expr, new_block };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,9 +288,9 @@ namespace gcc
|
|||||||
this->current_expression = null_pointer_node;
|
this->current_expression = null_pointer_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
void generic_visitor::visit(boot::string_literal *string)
|
void generic_visitor::visit(boot::number_literal<std::string> *string)
|
||||||
{
|
{
|
||||||
this->current_expression = build_string_literal(string->string().size() + 1, string->string().c_str());
|
this->current_expression = build_string_literal(string->number().size() + 1, string->number().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
tree generic_visitor::build_arithmetic_operation(boot::binary_expression *expression,
|
tree generic_visitor::build_arithmetic_operation(boot::binary_expression *expression,
|
||||||
@ -433,9 +443,12 @@ namespace gcc
|
|||||||
TREE_CONSTANT(definition_tree) = 1;
|
TREE_CONSTANT(definition_tree) = 1;
|
||||||
TREE_READONLY(definition_tree) = 1;
|
TREE_READONLY(definition_tree) = 1;
|
||||||
|
|
||||||
auto declaration_statement = build1_loc(definition_location, DECL_EXPR,
|
if (!scope.empty())
|
||||||
void_type_node, definition_tree);
|
{
|
||||||
append_to_statement_list(declaration_statement, &this->current_statements);
|
auto declaration_statement = build1_loc(definition_location, DECL_EXPR,
|
||||||
|
void_type_node, definition_tree);
|
||||||
|
this->scope.front().append_statement(declaration_statement);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -450,27 +463,9 @@ namespace gcc
|
|||||||
{
|
{
|
||||||
tree tree_type = build_type(definition->body());
|
tree tree_type = build_type(definition->body());
|
||||||
|
|
||||||
if (tree_type == NULL_TREE)
|
if (!this->symbol_map->enter(definition->identifier(), tree_type))
|
||||||
{
|
{
|
||||||
return;
|
error_at(get_location(&definition->position()),
|
||||||
}
|
|
||||||
location_t definition_location = get_location(&definition->position());
|
|
||||||
tree definition_tree = build_decl(definition_location, TYPE_DECL,
|
|
||||||
get_identifier(definition->identifier().c_str()), tree_type);
|
|
||||||
auto result = this->symbol_map->enter(definition->identifier(), tree_type);
|
|
||||||
|
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
DECL_CONTEXT(definition_tree) = this->main_fndecl;
|
|
||||||
variable_chain.append(definition_tree);
|
|
||||||
|
|
||||||
auto declaration_statement = build1_loc(definition_location, DECL_EXPR,
|
|
||||||
void_type_node, definition_tree);
|
|
||||||
append_to_statement_list(declaration_statement, &this->current_statements);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
error_at(definition_location,
|
|
||||||
"type '%s' already declared in this scope",
|
"type '%s' already declared in this scope",
|
||||||
definition->identifier().c_str());
|
definition->identifier().c_str());
|
||||||
}
|
}
|
||||||
@ -606,11 +601,11 @@ namespace gcc
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
DECL_CONTEXT(declaration_tree) = this->main_fndecl;
|
DECL_CONTEXT(declaration_tree) = this->main_fndecl;
|
||||||
variable_chain.append(declaration_tree);
|
this->scope.front().variables.append(declaration_tree);
|
||||||
|
|
||||||
auto declaration_statement = build1_loc(declaration_location, DECL_EXPR,
|
auto declaration_statement = build1_loc(declaration_location, DECL_EXPR,
|
||||||
void_type_node, declaration_tree);
|
void_type_node, declaration_tree);
|
||||||
append_to_statement_list(declaration_statement, &this->current_statements);
|
this->scope.front().append_statement(declaration_statement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -705,7 +700,7 @@ namespace gcc
|
|||||||
tree assignment = build2_loc(statement_location, MODIFY_EXPR,
|
tree assignment = build2_loc(statement_location, MODIFY_EXPR,
|
||||||
void_type_node, lvalue, this->current_expression);
|
void_type_node, lvalue, this->current_expression);
|
||||||
|
|
||||||
append_to_statement_list(assignment, &this->current_statements);
|
this->scope.front().append_statement(assignment);
|
||||||
this->current_expression = NULL_TREE;
|
this->current_expression = NULL_TREE;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -731,14 +726,16 @@ namespace gcc
|
|||||||
}
|
}
|
||||||
if (statement->alternative() != nullptr)
|
if (statement->alternative() != nullptr)
|
||||||
{
|
{
|
||||||
|
enter_scope();
|
||||||
for (const auto body_statement : *statement->alternative())
|
for (const auto body_statement : *statement->alternative())
|
||||||
{
|
{
|
||||||
body_statement->accept(this);
|
body_statement->accept(this);
|
||||||
}
|
}
|
||||||
|
tree_symbol_mapping mapping = leave_scope();
|
||||||
|
scope.front().append_statement(mapping.bind_expression());
|
||||||
}
|
}
|
||||||
|
|
||||||
tree endif_label_expr = build1(LABEL_EXPR, void_type_node, endif_label_decl);
|
tree endif_label_expr = build1(LABEL_EXPR, void_type_node, endif_label_decl);
|
||||||
append_to_statement_list(endif_label_expr, &this->current_statements);
|
this->scope.front().append_statement(endif_label_expr);
|
||||||
this->current_expression = NULL_TREE;
|
this->current_expression = NULL_TREE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -761,19 +758,22 @@ namespace gcc
|
|||||||
tree goto_else = build1(GOTO_EXPR, void_type_node, else_label_decl);
|
tree goto_else = build1(GOTO_EXPR, void_type_node, else_label_decl);
|
||||||
|
|
||||||
auto cond_expr = build3(COND_EXPR, void_type_node, this->current_expression, goto_then, goto_else);
|
auto cond_expr = build3(COND_EXPR, void_type_node, this->current_expression, goto_then, goto_else);
|
||||||
append_to_statement_list(cond_expr, &this->current_statements);
|
this->scope.front().append_statement(cond_expr);
|
||||||
|
|
||||||
tree then_label_expr = build1(LABEL_EXPR, void_type_node, then_label_decl);
|
tree then_label_expr = build1(LABEL_EXPR, void_type_node, then_label_decl);
|
||||||
append_to_statement_list(then_label_expr, &this->current_statements);
|
this->scope.front().append_statement(then_label_expr);
|
||||||
|
enter_scope();
|
||||||
|
|
||||||
for (const auto body_statement : branch.statements)
|
for (const auto body_statement : branch.statements)
|
||||||
{
|
{
|
||||||
body_statement->accept(this);
|
body_statement->accept(this);
|
||||||
}
|
}
|
||||||
append_to_statement_list(goto_endif, &this->current_statements);
|
tree_symbol_mapping mapping = leave_scope();
|
||||||
|
this->scope.front().append_statement(mapping.bind_expression());
|
||||||
|
this->scope.front().append_statement(goto_endif);
|
||||||
|
|
||||||
tree else_label_expr = build1(LABEL_EXPR, void_type_node, else_label_decl);
|
tree else_label_expr = build1(LABEL_EXPR, void_type_node, else_label_decl);
|
||||||
append_to_statement_list(else_label_expr, &this->current_statements);
|
this->scope.front().append_statement(else_label_expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
tree generic_visitor::build_label_decl(const char *name, location_t loc)
|
tree generic_visitor::build_label_decl(const char *name, location_t loc)
|
||||||
@ -798,13 +798,15 @@ namespace gcc
|
|||||||
this->current_expression = error_mark_node;
|
this->current_expression = error_mark_node;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
enter_scope();
|
||||||
|
|
||||||
auto prerequisite_location = get_location(&statement->body().prerequisite().position());
|
auto prerequisite_location = get_location(&statement->body().prerequisite().position());
|
||||||
auto body_location = get_location(&statement->position());
|
auto body_location = get_location(&statement->position());
|
||||||
|
|
||||||
auto prerequisite_label_decl = build_label_decl("while_check", prerequisite_location);
|
auto prerequisite_label_decl = build_label_decl("while_check", prerequisite_location);
|
||||||
auto prerequisite_label_expr = build1_loc(prerequisite_location, LABEL_EXPR,
|
auto prerequisite_label_expr = build1_loc(prerequisite_location, LABEL_EXPR,
|
||||||
void_type_node, prerequisite_label_decl);
|
void_type_node, prerequisite_label_decl);
|
||||||
append_to_statement_list(prerequisite_label_expr, &this->current_statements);
|
this->scope.front().append_statement(prerequisite_label_expr);
|
||||||
|
|
||||||
auto body_label_decl = build_label_decl("while_body", body_location);
|
auto body_label_decl = build_label_decl("while_body", body_location);
|
||||||
auto end_label_decl = build_label_decl("end_while", UNKNOWN_LOCATION);
|
auto end_label_decl = build_label_decl("end_while", UNKNOWN_LOCATION);
|
||||||
@ -816,21 +818,24 @@ namespace gcc
|
|||||||
|
|
||||||
auto cond_expr = build3_loc(prerequisite_location, COND_EXPR,
|
auto cond_expr = build3_loc(prerequisite_location, COND_EXPR,
|
||||||
void_type_node, this->current_expression, goto_body, goto_end);
|
void_type_node, this->current_expression, goto_body, goto_end);
|
||||||
append_to_statement_list(cond_expr, &this->current_statements);
|
this->scope.front().append_statement(cond_expr);
|
||||||
|
|
||||||
auto body_label_expr = build1_loc(body_location, LABEL_EXPR,
|
auto body_label_expr = build1_loc(body_location, LABEL_EXPR,
|
||||||
void_type_node, body_label_decl);
|
void_type_node, body_label_decl);
|
||||||
append_to_statement_list(body_label_expr, &this->current_statements);
|
this->scope.front().append_statement(body_label_expr);
|
||||||
|
|
||||||
for (const auto body_statement : statement->body().statements)
|
for (const auto body_statement : statement->body().statements)
|
||||||
{
|
{
|
||||||
body_statement->accept(this);
|
body_statement->accept(this);
|
||||||
}
|
}
|
||||||
|
tree_symbol_mapping mapping = leave_scope();
|
||||||
|
this->scope.front().append_statement(mapping.bind_expression());
|
||||||
|
|
||||||
auto goto_check = build1(GOTO_EXPR, void_type_node, prerequisite_label_decl);
|
auto goto_check = build1(GOTO_EXPR, void_type_node, prerequisite_label_decl);
|
||||||
append_to_statement_list(goto_check, &this->current_statements);
|
this->scope.front().append_statement(goto_check);
|
||||||
|
|
||||||
auto endif_label_expr = build1(LABEL_EXPR, void_type_node, end_label_decl);
|
auto endif_label_expr = build1(LABEL_EXPR, void_type_node, end_label_decl);
|
||||||
append_to_statement_list(endif_label_expr, &this->current_statements);
|
this->scope.front().append_statement(endif_label_expr);
|
||||||
|
|
||||||
this->current_expression = NULL_TREE;
|
this->current_expression = NULL_TREE;
|
||||||
}
|
}
|
||||||
@ -838,7 +843,8 @@ namespace gcc
|
|||||||
void generic_visitor::visit(boot::call_statement *statement)
|
void generic_visitor::visit(boot::call_statement *statement)
|
||||||
{
|
{
|
||||||
statement->body().accept(this);
|
statement->body().accept(this);
|
||||||
append_to_statement_list(this->current_expression, &this->current_statements);
|
this->scope.front().append_statement(this->current_expression);
|
||||||
|
this->current_expression = NULL_TREE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void generic_visitor::visit(boot::return_statement *statement)
|
void generic_visitor::visit(boot::return_statement *statement)
|
||||||
@ -854,7 +860,18 @@ namespace gcc
|
|||||||
tree set_result = build2(INIT_EXPR, void_type_node, DECL_RESULT(main_fndecl),
|
tree set_result = build2(INIT_EXPR, void_type_node, DECL_RESULT(main_fndecl),
|
||||||
this->current_expression);
|
this->current_expression);
|
||||||
tree return_stmt = build1(RETURN_EXPR, void_type_node, set_result);
|
tree return_stmt = build1(RETURN_EXPR, void_type_node, set_result);
|
||||||
append_to_statement_list(return_stmt, &this->current_statements);
|
this->scope.front().append_statement(return_stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void generic_visitor::visit(boot::defer_statement *statement)
|
||||||
|
{
|
||||||
|
enter_scope();
|
||||||
|
for (boot::statement *const body_statement : statement->statements)
|
||||||
|
{
|
||||||
|
body_statement->accept(this);
|
||||||
|
}
|
||||||
|
tree_symbol_mapping mapping = leave_scope();
|
||||||
|
scope.front().defer(mapping.bind_expression());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,11 @@ namespace gcc
|
|||||||
TREE_CHAIN(this->last) = t;
|
TREE_CHAIN(this->last) = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void block_chain::chain(tree t)
|
||||||
|
{
|
||||||
|
BLOCK_CHAIN(this->last) = t;
|
||||||
|
}
|
||||||
|
|
||||||
tree_symbol_mapping::tree_symbol_mapping(tree bind_expression, tree block)
|
tree_symbol_mapping::tree_symbol_mapping(tree bind_expression, tree block)
|
||||||
: m_bind_expression(bind_expression), m_block(block)
|
: m_bind_expression(bind_expression), m_block(block)
|
||||||
{
|
{
|
||||||
@ -75,6 +80,47 @@ namespace gcc
|
|||||||
return m_block;
|
return m_block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
block_scope::block_scope()
|
||||||
|
: m_statement_list(alloc_stmt_list())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void block_scope::append_statement(tree statement_tree)
|
||||||
|
{
|
||||||
|
if (!defers.empty())
|
||||||
|
{
|
||||||
|
append_to_statement_list(statement_tree, &this->defers.front().second);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
append_to_statement_list(statement_tree, &this->m_statement_list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void block_scope::defer(tree statement_tree)
|
||||||
|
{
|
||||||
|
defers.push_front({ statement_tree, alloc_stmt_list() });
|
||||||
|
}
|
||||||
|
|
||||||
|
tree block_scope::chain_defer()
|
||||||
|
{
|
||||||
|
if (this->defers.empty())
|
||||||
|
{
|
||||||
|
return m_statement_list;
|
||||||
|
}
|
||||||
|
std::forward_list<std::pair<tree, tree>>::iterator defer_iterator =
|
||||||
|
this->defers.begin();
|
||||||
|
tree defer_tree = build2(TRY_FINALLY_EXPR, void_type_node, defer_iterator->second, defer_iterator->first);
|
||||||
|
|
||||||
|
++defer_iterator;
|
||||||
|
for (; defer_iterator != this->defers.end(); ++defer_iterator)
|
||||||
|
{
|
||||||
|
append_to_statement_list(defer_tree, &defer_iterator->second);
|
||||||
|
defer_tree = build2(TRY_FINALLY_EXPR, void_type_node, defer_iterator->second, defer_iterator->first);
|
||||||
|
}
|
||||||
|
return build2(COMPOUND_EXPR, TREE_TYPE(defer_tree), m_statement_list, defer_tree);
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<boot::symbol_table<tree>> builtin_symbol_table()
|
std::shared_ptr<boot::symbol_table<tree>> builtin_symbol_table()
|
||||||
{
|
{
|
||||||
std::shared_ptr<boot::symbol_table<tree>> initial_table =
|
std::shared_ptr<boot::symbol_table<tree>> initial_table =
|
||||||
|
@ -64,7 +64,7 @@ namespace boot
|
|||||||
class dereference_expression;
|
class dereference_expression;
|
||||||
template<typename T>
|
template<typename T>
|
||||||
class number_literal;
|
class number_literal;
|
||||||
class string_literal;
|
class defer_statement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for AST visitors.
|
* Interface for AST visitors.
|
||||||
@ -83,6 +83,7 @@ namespace boot
|
|||||||
virtual void visit(if_statement *) = 0;
|
virtual void visit(if_statement *) = 0;
|
||||||
virtual void visit(while_statement *) = 0;
|
virtual void visit(while_statement *) = 0;
|
||||||
virtual void visit(return_statement *) = 0;
|
virtual void visit(return_statement *) = 0;
|
||||||
|
virtual void visit(defer_statement *) = 0;
|
||||||
virtual void visit(block *) = 0;
|
virtual void visit(block *) = 0;
|
||||||
virtual void visit(program *) = 0;
|
virtual void visit(program *) = 0;
|
||||||
virtual void visit(binary_expression *) = 0;
|
virtual void visit(binary_expression *) = 0;
|
||||||
@ -102,7 +103,7 @@ namespace boot
|
|||||||
virtual void visit(number_literal<bool> *) = 0;
|
virtual void visit(number_literal<bool> *) = 0;
|
||||||
virtual void visit(number_literal<unsigned char> *) = 0;
|
virtual void visit(number_literal<unsigned char> *) = 0;
|
||||||
virtual void visit(number_literal<std::nullptr_t> *) = 0;
|
virtual void visit(number_literal<std::nullptr_t> *) = 0;
|
||||||
virtual void visit(string_literal *) = 0;
|
virtual void visit(number_literal<std::string> *) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,6 +123,7 @@ namespace boot
|
|||||||
virtual void visit(if_statement *) override;
|
virtual void visit(if_statement *) override;
|
||||||
virtual void visit(while_statement *) override;
|
virtual void visit(while_statement *) override;
|
||||||
virtual void visit(return_statement *) override;
|
virtual void visit(return_statement *) override;
|
||||||
|
virtual void visit(defer_statement *defer) override;
|
||||||
virtual void visit(block *block) override;
|
virtual void visit(block *block) override;
|
||||||
virtual void visit(program *program) override;
|
virtual void visit(program *program) override;
|
||||||
virtual void visit(binary_expression *expression) override;
|
virtual void visit(binary_expression *expression) override;
|
||||||
@ -141,7 +143,7 @@ namespace boot
|
|||||||
virtual void visit(number_literal<bool> *) override;
|
virtual void visit(number_literal<bool> *) override;
|
||||||
virtual void visit(number_literal<unsigned char> *) override;
|
virtual void visit(number_literal<unsigned char> *) override;
|
||||||
virtual void visit(number_literal<std::nullptr_t> *) override;
|
virtual void visit(number_literal<std::nullptr_t> *) override;
|
||||||
virtual void visit(string_literal *) override;
|
virtual void visit(number_literal<std::string> *) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -676,21 +678,21 @@ namespace boot
|
|||||||
visitor->visit(this);
|
visitor->visit(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
T number() const
|
const T& number() const
|
||||||
{
|
{
|
||||||
return m_number;
|
return m_number;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class string_literal : public literal
|
class defer_statement : public statement
|
||||||
{
|
{
|
||||||
std::string m_string;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
string_literal(const struct position position, const std::string& value);
|
std::vector<statement *> statements;
|
||||||
|
|
||||||
|
defer_statement(const struct position position);
|
||||||
virtual void accept(parser_visitor *visitor) override;
|
virtual void accept(parser_visitor *visitor) override;
|
||||||
|
|
||||||
const std::string& string() const;
|
virtual ~defer_statement() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class binary_expression : public expression
|
class binary_expression : public expression
|
||||||
|
@ -39,6 +39,8 @@ namespace boot
|
|||||||
const std::list<std::unique_ptr<struct error>>& errors() const noexcept;
|
const std::list<std::unique_ptr<struct error>>& errors() const noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<char> escape_char(char escape);
|
constexpr char escape_invalid_char = '\xff';
|
||||||
|
|
||||||
|
char escape_char(char escape);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
#include "tree.h"
|
#include "tree.h"
|
||||||
#include "tree-iterator.h"
|
#include "tree-iterator.h"
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <forward_list>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace elna
|
namespace elna
|
||||||
@ -19,11 +19,10 @@ namespace gcc
|
|||||||
{
|
{
|
||||||
class generic_visitor final : public boot::empty_visitor
|
class generic_visitor final : public boot::empty_visitor
|
||||||
{
|
{
|
||||||
tree current_statements{ NULL_TREE };
|
std::forward_list<block_scope> scope;
|
||||||
tree current_expression{ NULL_TREE };
|
tree current_expression{ NULL_TREE };
|
||||||
std::shared_ptr<boot::symbol_table<tree>> symbol_map;
|
std::shared_ptr<boot::symbol_table<tree>> symbol_map;
|
||||||
tree main_fndecl{ NULL_TREE };
|
tree main_fndecl{ NULL_TREE };
|
||||||
tree_chain variable_chain;
|
|
||||||
|
|
||||||
tree build_label_decl(const char *name, location_t loc);
|
tree build_label_decl(const char *name, location_t loc);
|
||||||
tree build_type(boot::type_expression& type);
|
tree build_type(boot::type_expression& type);
|
||||||
@ -58,7 +57,7 @@ namespace gcc
|
|||||||
void visit(boot::number_literal<bool> *boolean) override;
|
void visit(boot::number_literal<bool> *boolean) override;
|
||||||
void visit(boot::number_literal<unsigned char> *character) override;
|
void visit(boot::number_literal<unsigned char> *character) override;
|
||||||
void visit(boot::number_literal<std::nullptr_t> *) override;
|
void visit(boot::number_literal<std::nullptr_t> *) override;
|
||||||
void visit(boot::string_literal *string) override;
|
void visit(boot::number_literal<std::string> *string) override;
|
||||||
void visit(boot::binary_expression *expression) override;
|
void visit(boot::binary_expression *expression) override;
|
||||||
void visit(boot::unary_expression *expression) override;
|
void visit(boot::unary_expression *expression) override;
|
||||||
void visit(boot::constant_definition *definition) override;
|
void visit(boot::constant_definition *definition) override;
|
||||||
@ -73,6 +72,7 @@ namespace gcc
|
|||||||
void visit(boot::while_statement *statement) override;
|
void visit(boot::while_statement *statement) override;
|
||||||
void visit(boot::call_statement *statement) override;
|
void visit(boot::call_statement *statement) override;
|
||||||
void visit(boot::return_statement *statement) override;
|
void visit(boot::return_statement *statement) override;
|
||||||
|
void visit(boot::defer_statement *statement) override;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <forward_list>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
#include "coretypes.h"
|
#include "coretypes.h"
|
||||||
#include "tree.h"
|
#include "tree.h"
|
||||||
|
#include "tree-iterator.h"
|
||||||
|
|
||||||
#include "elna/boot/ast.h"
|
#include "elna/boot/ast.h"
|
||||||
#include "elna/boot/symbol.h"
|
#include "elna/boot/symbol.h"
|
||||||
@ -43,6 +46,13 @@ namespace gcc
|
|||||||
|
|
||||||
class tree_chain final : public tree_chain_base
|
class tree_chain final : public tree_chain_base
|
||||||
{
|
{
|
||||||
|
protected:
|
||||||
|
void chain(tree t) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class block_chain final : public tree_chain_base
|
||||||
|
{
|
||||||
|
protected:
|
||||||
void chain(tree t) override;
|
void chain(tree t) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -58,6 +68,22 @@ namespace gcc
|
|||||||
tree block();
|
tree block();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class block_scope
|
||||||
|
{
|
||||||
|
tree m_statement_list{ NULL_TREE };
|
||||||
|
std::forward_list<std::pair<tree, tree>> defers;
|
||||||
|
|
||||||
|
public:
|
||||||
|
tree_chain variables;
|
||||||
|
block_chain blocks;
|
||||||
|
|
||||||
|
block_scope();
|
||||||
|
|
||||||
|
void append_statement(tree statement_tree);
|
||||||
|
void defer(tree statement_tree);
|
||||||
|
tree chain_defer();
|
||||||
|
};
|
||||||
|
|
||||||
std::shared_ptr<boot::symbol_table<tree>> builtin_symbol_table();
|
std::shared_ptr<boot::symbol_table<tree>> builtin_symbol_table();
|
||||||
|
|
||||||
tree do_pointer_arithmetic(boot::binary_operator binary_operator, tree left, tree right);
|
tree do_pointer_arithmetic(boot::binary_operator binary_operator, tree left, tree right);
|
||||||
|
21
source.elna
21
source.elna
@ -14,7 +14,7 @@ const
|
|||||||
TOKEN_PLUS = 43; TOKEN_MINUS = 44; TOKEN_MULTIPLICATION = 45; TOKEN_DIVISION = 46;
|
TOKEN_PLUS = 43; TOKEN_MINUS = 44; TOKEN_MULTIPLICATION = 45; TOKEN_DIVISION = 46;
|
||||||
TOKEN_REMAINDER = 47; TOKEN_ASSIGNMENT = 48; TOKEN_COLON = 49; TOKEN_HAT = 50;
|
TOKEN_REMAINDER = 47; TOKEN_ASSIGNMENT = 48; TOKEN_COLON = 49; TOKEN_HAT = 50;
|
||||||
TOKEN_AT = 51; TOKEN_COMMENT = 52; TOKEN_INTEGER = 53; TOKEN_WORD = 54;
|
TOKEN_AT = 51; TOKEN_COMMENT = 52; TOKEN_INTEGER = 53; TOKEN_WORD = 54;
|
||||||
TOKEN_CHARACTER = 55; TOKEN_STRING = 56;
|
TOKEN_CHARACTER = 55; TOKEN_STRING = 56, TOKEN_DEFER = 57;
|
||||||
|
|
||||||
type
|
type
|
||||||
Position = record
|
Position = record
|
||||||
@ -41,7 +41,8 @@ type
|
|||||||
end,
|
end,
|
||||||
CommandLine = record
|
CommandLine = record
|
||||||
input: pointer to Char;
|
input: pointer to Char;
|
||||||
tokenize: Bool
|
tokenize: Bool;
|
||||||
|
syntax_tree: Bool
|
||||||
end,
|
end,
|
||||||
Literal = record
|
Literal = record
|
||||||
value: Int
|
value: Int
|
||||||
@ -457,6 +458,8 @@ begin
|
|||||||
write_s("c>")
|
write_s("c>")
|
||||||
elsif current_token^.kind = TOKEN_STRING then
|
elsif current_token^.kind = TOKEN_STRING then
|
||||||
write_s("\"...\"")
|
write_s("\"...\"")
|
||||||
|
elsif current_token^.kind = TOKEN_DEFER then
|
||||||
|
write_s("DEFER")
|
||||||
else
|
else
|
||||||
write_s("UNKNOWN<");
|
write_s("UNKNOWN<");
|
||||||
write_i(current_token^.kind);
|
write_i(current_token^.kind);
|
||||||
@ -533,6 +536,8 @@ begin
|
|||||||
current_token.kind := TOKEN_AS
|
current_token.kind := TOKEN_AS
|
||||||
elsif strncmp("sizeof", input_pointer, token_length) = 0 then
|
elsif strncmp("sizeof", input_pointer, token_length) = 0 then
|
||||||
current_token.kind := TOKEN_SIZEOF
|
current_token.kind := TOKEN_SIZEOF
|
||||||
|
elsif strncmp("defer", input_pointer, token_length) = 0 then
|
||||||
|
current_token.kind := TOKEN_DEFER
|
||||||
else
|
else
|
||||||
current_token.kind := TOKEN_IDENTIFIER;
|
current_token.kind := TOKEN_IDENTIFIER;
|
||||||
current_token.value.string_value := cast(calloc(token_length + 1, 1) as pointer to Char);
|
current_token.value.string_value := cast(calloc(token_length + 1, 1) as pointer to Char);
|
||||||
@ -765,6 +770,7 @@ begin
|
|||||||
i := 1;
|
i := 1;
|
||||||
result := cast(malloc(sizeof(CommandLine)) as pointer to CommandLine);
|
result := cast(malloc(sizeof(CommandLine)) as pointer to CommandLine);
|
||||||
result^.tokenize := false;
|
result^.tokenize := false;
|
||||||
|
result^.syntax_tree := false;
|
||||||
result^.input := nil;
|
result^.input := nil;
|
||||||
|
|
||||||
while i < argc do
|
while i < argc do
|
||||||
@ -772,6 +778,8 @@ begin
|
|||||||
|
|
||||||
if strcmp(parameter^, "--tokenize") = 0 then
|
if strcmp(parameter^, "--tokenize") = 0 then
|
||||||
result^.tokenize := true
|
result^.tokenize := true
|
||||||
|
elsif strcmp(parameter^, "--syntax-tree") = 0 then
|
||||||
|
result^.syntax_tree := true
|
||||||
elsif parameter^^ <> '-' then
|
elsif parameter^^ <> '-' then
|
||||||
result^.input := parameter^
|
result^.input := parameter^
|
||||||
else
|
else
|
||||||
@ -802,10 +810,10 @@ var
|
|||||||
command_line: pointer to CommandLine;
|
command_line: pointer to CommandLine;
|
||||||
begin
|
begin
|
||||||
command_line := parse_command_line(argc, argv);
|
command_line := parse_command_line(argc, argv);
|
||||||
|
if command_line = nil then
|
||||||
if cast(command_line as Word) = 0u then
|
|
||||||
return 2
|
return 2
|
||||||
end;
|
end;
|
||||||
|
|
||||||
input := read_source(command_line^.input);
|
input := read_source(command_line^.input);
|
||||||
if input = nil then
|
if input = nil then
|
||||||
perror(command_line^.input);
|
perror(command_line^.input);
|
||||||
@ -816,8 +824,9 @@ begin
|
|||||||
if command_line^.tokenize then
|
if command_line^.tokenize then
|
||||||
print_tokens(tokens, tokens_size)
|
print_tokens(tokens, tokens_size)
|
||||||
end;
|
end;
|
||||||
|
if command_line^.syntax_tree then
|
||||||
parse_program(@tokens, @tokens_size);
|
parse_program(@tokens, @tokens_size)
|
||||||
|
end;
|
||||||
return 0
|
return 0
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user