🐛 fix codegen bug in condition & mcall
This commit is contained in:
parent
b6886cc957
commit
af3ade5a41
|
@ -66,6 +66,7 @@ public:
|
||||||
nd_loc.begin_column = column;
|
nd_loc.begin_column = column;
|
||||||
}
|
}
|
||||||
const span& get_location() const {return nd_loc;}
|
const span& get_location() const {return nd_loc;}
|
||||||
|
const u32 get_line() const {return nd_loc.begin_line;}
|
||||||
expr_type get_type() const {return nd_type;}
|
expr_type get_type() const {return nd_type;}
|
||||||
void update_location(const span& location) {
|
void update_location(const span& location) {
|
||||||
nd_loc.end_line = location.end_line;
|
nd_loc.end_line = location.end_line;
|
||||||
|
|
|
@ -1,31 +1,12 @@
|
||||||
#include "nasal_new_codegen.h"
|
#include "nasal_new_codegen.h"
|
||||||
|
|
||||||
bool codegen::check_memory_reachable(expr* node) {
|
|
||||||
if (node->get_type()==expr_type::ast_call) {
|
|
||||||
const auto tmp=((call_expr*)node)->get_calls().back();
|
|
||||||
if (tmp->get_type()==expr_type::ast_callf) {
|
|
||||||
die("bad left-value with function call", node->get_location());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (tmp->get_type()==expr_type::ast_callv &&
|
|
||||||
(((call_vector*)tmp)->get_slices().size()!=1 ||
|
|
||||||
((call_vector*)tmp)->get_slices()[0]->get_end())) {
|
|
||||||
die("bad left-value with subvec", node->get_location());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (node->get_type()!=expr_type::ast_id) {
|
|
||||||
die("bad left-value", node->get_location());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void codegen::check_id_exist(identifier* node) {
|
void codegen::check_id_exist(identifier* node) {
|
||||||
const auto& name = node->get_name();
|
const auto& name = node->get_name();
|
||||||
for(u32 i = 0; builtin[i].name; ++i) {
|
for(u32 i = 0; builtin[i].name; ++i) {
|
||||||
if (builtin[i].name==name) {
|
if (builtin[i].name==name) {
|
||||||
if (local.empty()) {
|
if (local.empty()) {
|
||||||
die("useless native function used in global scope", node->get_location());
|
die("native function used in global scope",
|
||||||
|
node->get_location());
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -40,7 +21,9 @@ void codegen::check_id_exist(identifier* node) {
|
||||||
if (global_find(name)>=0) {
|
if (global_find(name)>=0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
die("undefined symbol \"" + name + "\", and this symbol is useless here", node->get_location());
|
die("undefined symbol \"" + name +
|
||||||
|
"\", and this symbol is useless here",
|
||||||
|
node->get_location());
|
||||||
}
|
}
|
||||||
|
|
||||||
void codegen::regist_num(const f64 num) {
|
void codegen::regist_num(const f64 num) {
|
||||||
|
@ -64,6 +47,7 @@ void codegen::find_symbol(code_block* node) {
|
||||||
for(const auto& i : finder->do_find(node)) {
|
for(const auto& i : finder->do_find(node)) {
|
||||||
add_sym(i);
|
add_sym(i);
|
||||||
}
|
}
|
||||||
|
delete finder;
|
||||||
}
|
}
|
||||||
|
|
||||||
void codegen::add_sym(const std::string& name) {
|
void codegen::add_sym(const std::string& name) {
|
||||||
|
@ -86,11 +70,11 @@ i32 codegen::local_find(const std::string& name) {
|
||||||
if (local.empty()) {
|
if (local.empty()) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return local.back().count(name)?local.back()[name]:-1;
|
return local.back().count(name)? local.back().at(name):-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
i32 codegen::global_find(const std::string& name) {
|
i32 codegen::global_find(const std::string& name) {
|
||||||
return global.count(name)?global[name]:-1;
|
return global.count(name)? global.at(name):-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
i32 codegen::upvalue_find(const std::string& name) {
|
i32 codegen::upvalue_find(const std::string& name) {
|
||||||
|
@ -103,31 +87,31 @@ i32 codegen::upvalue_find(const std::string& name) {
|
||||||
auto iter = local.begin();
|
auto iter = local.begin();
|
||||||
for(u32 i = 0; i<size-1; ++i, ++iter) {
|
for(u32 i = 0; i<size-1; ++i, ++iter) {
|
||||||
if (iter->count(name)) {
|
if (iter->count(name)) {
|
||||||
index=((i<<16)|(*iter)[name]);
|
index = ((i<<16)|(*iter).at(name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
void codegen::gen(u8 op, u32 num, u32 line) {
|
void codegen::gen(u8 operation_code, u32 num, u32 line) {
|
||||||
code.push_back({op, fileindex, num, line});
|
code.push_back({operation_code, fileindex, num, line});
|
||||||
}
|
}
|
||||||
|
|
||||||
void codegen::num_gen(number_literal* node) {
|
void codegen::num_gen(number_literal* node) {
|
||||||
f64 num = node->get_number();
|
f64 num = node->get_number();
|
||||||
regist_num(num);
|
regist_num(num);
|
||||||
gen(op_pnum,num_table[num], node->get_location().begin_line);
|
gen(op_pnum,num_table.at(num), node->get_location().begin_line);
|
||||||
}
|
}
|
||||||
|
|
||||||
void codegen::str_gen(string_literal* node) {
|
void codegen::str_gen(string_literal* node) {
|
||||||
regist_str(node->get_content());
|
regist_str(node->get_content());
|
||||||
gen(op_pstr, str_table[node->get_content()], node->get_location().begin_line);
|
gen(op_pstr, str_table.at(node->get_content()), node->get_location().begin_line);
|
||||||
}
|
}
|
||||||
|
|
||||||
void codegen::bool_gen(bool_literal* node) {
|
void codegen::bool_gen(bool_literal* node) {
|
||||||
f64 num = node->get_flag()? 1:0;
|
f64 num = node->get_flag()? 1:0;
|
||||||
regist_num(num);
|
regist_num(num);
|
||||||
gen(op_pnum, num_table[num], node->get_location().begin_line);
|
gen(op_pnum, num_table.at(num), node->get_location().begin_line);
|
||||||
}
|
}
|
||||||
|
|
||||||
void codegen::vec_gen(vector_expr* node) {
|
void codegen::vec_gen(vector_expr* node) {
|
||||||
|
@ -141,9 +125,9 @@ void codegen::hash_gen(hash_expr* node) {
|
||||||
gen(op_newh, 0, node->get_location().begin_line);
|
gen(op_newh, 0, node->get_location().begin_line);
|
||||||
for(auto child : node->get_members()) {
|
for(auto child : node->get_members()) {
|
||||||
calc_gen(child->get_value());
|
calc_gen(child->get_value());
|
||||||
const std::string& str=child->get_name();
|
const auto& field_name = child->get_name();
|
||||||
regist_str(str);
|
regist_str(field_name);
|
||||||
gen(op_happ, str_table[str], child->get_location().begin_line);
|
gen(op_happ, str_table.at(field_name), child->get_location().begin_line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,22 +137,30 @@ void codegen::func_gen(function* node) {
|
||||||
bool checked_dynamic = false;
|
bool checked_dynamic = false;
|
||||||
std::unordered_map<std::string, bool> argname;
|
std::unordered_map<std::string, bool> argname;
|
||||||
for(auto tmp : node->get_parameter_list()) {
|
for(auto tmp : node->get_parameter_list()) {
|
||||||
if (tmp->get_parameter_type()==parameter::param_type::default_parameter) {
|
if (tmp->get_parameter_type()==
|
||||||
|
parameter::param_type::default_parameter) {
|
||||||
checked_default=true;
|
checked_default=true;
|
||||||
} else if (tmp->get_parameter_type()==parameter::param_type::dynamic_parameter) {
|
} else if (tmp->get_parameter_type()==
|
||||||
|
parameter::param_type::dynamic_parameter) {
|
||||||
checked_dynamic=true;
|
checked_dynamic=true;
|
||||||
}
|
}
|
||||||
// check default parameter and dynamic parameter
|
// check default parameter and dynamic parameter
|
||||||
if (checked_default && tmp->get_parameter_type()!=parameter::param_type::default_parameter) {
|
if (checked_default &&
|
||||||
die("must use default parameters here", tmp->get_location());
|
tmp->get_parameter_type()!=
|
||||||
|
parameter::param_type::default_parameter) {
|
||||||
|
die("must use default parameter here",
|
||||||
|
tmp->get_location());
|
||||||
}
|
}
|
||||||
if (checked_dynamic && tmp!=node->get_parameter_list().back()) {
|
if (checked_dynamic &&
|
||||||
die("dynamic parameter must be the last one", tmp->get_location());
|
tmp!=node->get_parameter_list().back()) {
|
||||||
|
die("dynamic parameter must be the last one",
|
||||||
|
tmp->get_location());
|
||||||
}
|
}
|
||||||
// check redefinition
|
// check redefinition
|
||||||
const auto& name = tmp->get_parameter_name();
|
const auto& name = tmp->get_parameter_name();
|
||||||
if (argname.count(name)) {
|
if (argname.count(name)) {
|
||||||
die("redefinition of parameter: "+name, tmp->get_location());
|
die("redefinition of parameter: " + name,
|
||||||
|
tmp->get_location());
|
||||||
} else {
|
} else {
|
||||||
argname[name] = true;
|
argname[name] = true;
|
||||||
}
|
}
|
||||||
|
@ -187,25 +179,26 @@ void codegen::func_gen(function* node) {
|
||||||
local.push_back({{"me", 0}});
|
local.push_back({{"me", 0}});
|
||||||
|
|
||||||
// generate parameter list
|
// generate parameter list
|
||||||
for(auto& tmp : node->get_parameter_list()) {
|
for(auto tmp : node->get_parameter_list()) {
|
||||||
const auto& str = tmp->get_parameter_name();
|
const auto& name = tmp->get_parameter_name();
|
||||||
if (str=="me") {
|
if (name=="me") {
|
||||||
die("\"me\" should not be a parameter", tmp->get_location());
|
die("\"me\" should not be a parameter",
|
||||||
|
tmp->get_location());
|
||||||
}
|
}
|
||||||
regist_str(str);
|
regist_str(name);
|
||||||
switch(tmp->get_parameter_type()) {
|
switch(tmp->get_parameter_type()) {
|
||||||
case parameter::param_type::normal_parameter:
|
case parameter::param_type::normal_parameter:
|
||||||
gen(op_para, str_table[str], tmp->get_location().begin_line);
|
gen(op_para, str_table.at(name), tmp->get_location().begin_line);
|
||||||
break;
|
break;
|
||||||
case parameter::param_type::default_parameter:
|
case parameter::param_type::default_parameter:
|
||||||
calc_gen(tmp->get_default_value());
|
calc_gen(tmp->get_default_value());
|
||||||
gen(op_deft, str_table[str], tmp->get_location().begin_line);
|
gen(op_deft, str_table.at(name), tmp->get_location().begin_line);
|
||||||
break;
|
break;
|
||||||
case parameter::param_type::dynamic_parameter:
|
case parameter::param_type::dynamic_parameter:
|
||||||
gen(op_dyn, str_table[str], tmp->get_location().begin_line);
|
gen(op_dyn, str_table.at(name), tmp->get_location().begin_line);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
add_sym(str);
|
add_sym(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
code[newf].num = code.size()+1; // entry
|
code[newf].num = code.size()+1; // entry
|
||||||
|
@ -221,7 +214,8 @@ void codegen::func_gen(function* node) {
|
||||||
in_iterloop.pop();
|
in_iterloop.pop();
|
||||||
code[lsize].num = local.back().size();
|
code[lsize].num = local.back().size();
|
||||||
if (local.back().size()>=STACK_DEPTH) {
|
if (local.back().size()>=STACK_DEPTH) {
|
||||||
die("too many local variants: "+std::to_string(local.back().size()), block->get_location());
|
die("too many local variants: " +
|
||||||
|
std::to_string(local.back().size()), block->get_location());
|
||||||
}
|
}
|
||||||
local.pop_back();
|
local.pop_back();
|
||||||
|
|
||||||
|
@ -253,7 +247,8 @@ void codegen::call_id(identifier* node) {
|
||||||
if (builtin[i].name==name) {
|
if (builtin[i].name==name) {
|
||||||
gen(op_callb, i, node->get_location().begin_line);
|
gen(op_callb, i, node->get_location().begin_line);
|
||||||
if (local.empty()) {
|
if (local.empty()) {
|
||||||
die("should warp native function in local scope", node->get_location());
|
die("should warp native function in local scope",
|
||||||
|
node->get_location());
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -276,7 +271,7 @@ void codegen::call_id(identifier* node) {
|
||||||
|
|
||||||
void codegen::call_hash_gen(call_hash* node) {
|
void codegen::call_hash_gen(call_hash* node) {
|
||||||
regist_str(node->get_field());
|
regist_str(node->get_field());
|
||||||
gen(op_callh, str_table[node->get_field()], node->get_location().begin_line);
|
gen(op_callh, str_table.at(node->get_field()), node->get_location().begin_line);
|
||||||
}
|
}
|
||||||
|
|
||||||
void codegen::call_vec(call_vector* node) {
|
void codegen::call_vec(call_vector* node) {
|
||||||
|
@ -307,9 +302,9 @@ void codegen::call_func(call_function* node) {
|
||||||
gen(op_newh, 0, node->get_location().begin_line);
|
gen(op_newh, 0, node->get_location().begin_line);
|
||||||
for(auto child : node->get_argument()) {
|
for(auto child : node->get_argument()) {
|
||||||
calc_gen(((hash_pair*)child)->get_value());
|
calc_gen(((hash_pair*)child)->get_value());
|
||||||
const auto& str = ((hash_pair*)child)->get_name();
|
const auto& field_name = ((hash_pair*)child)->get_name();
|
||||||
regist_str(str);
|
regist_str(field_name);
|
||||||
gen(op_happ, str_table[str], child->get_location().begin_line);
|
gen(op_happ, str_table.at(field_name), child->get_location().begin_line);
|
||||||
}
|
}
|
||||||
gen(op_callfh, 0, node->get_location().begin_line);
|
gen(op_callfh, 0, node->get_location().begin_line);
|
||||||
} else {
|
} else {
|
||||||
|
@ -328,28 +323,35 @@ void codegen::call_func(call_function* node) {
|
||||||
* so we use another way to avoid gc-caused SIGSEGV: reserve m-called value on stack.
|
* so we use another way to avoid gc-caused SIGSEGV: reserve m-called value on stack.
|
||||||
* you could see the notes in `vm::opr_mcallv()`.
|
* you could see the notes in `vm::opr_mcallv()`.
|
||||||
*/
|
*/
|
||||||
void codegen::mcall(call_expr* node) {
|
void codegen::mcall(expr* node) {
|
||||||
if (!check_memory_reachable(node)) {
|
if (node->get_type()!=expr_type::ast_id &&
|
||||||
|
node->get_type()!=expr_type::ast_call) {
|
||||||
|
die("bad left-value", node->get_location());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (node->get_first()->get_type()==expr_type::ast_id &&
|
if (node->get_type()==expr_type::ast_id) {
|
||||||
!node->get_calls().size()) {
|
mcall_id((identifier*)node);
|
||||||
mcall_id((identifier*)node->get_first());
|
return;
|
||||||
}
|
}
|
||||||
calc_gen(node->get_first());
|
auto call_node = (call_expr*)node;
|
||||||
for(usize i = 0; i<node->get_calls().size()-1; ++i) {
|
calc_gen(call_node->get_first());
|
||||||
auto tmp = node->get_calls()[i];
|
for(usize i = 0; i<call_node->get_calls().size()-1; ++i) {
|
||||||
|
auto tmp = call_node->get_calls()[i];
|
||||||
switch(tmp->get_type()) {
|
switch(tmp->get_type()) {
|
||||||
case expr_type::ast_callh: call_hash_gen((call_hash*)tmp); break;
|
case expr_type::ast_callh: call_hash_gen((call_hash*)tmp); break;
|
||||||
case expr_type::ast_callv: call_vec((call_vector*)tmp); break;
|
case expr_type::ast_callv: call_vec((call_vector*)tmp); break;
|
||||||
case expr_type::ast_callf: call_func((call_function*)tmp); break;
|
case expr_type::ast_callf: call_func((call_function*)tmp); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto tmp = node->get_calls().back();
|
auto tmp = call_node->get_calls().back();
|
||||||
if (tmp->get_type()==expr_type::ast_callh) {
|
if (tmp->get_type()==expr_type::ast_callh) {
|
||||||
mcall_hash((call_hash*)tmp);
|
mcall_hash((call_hash*)tmp);
|
||||||
} else if (tmp->get_type()==expr_type::ast_callv) {
|
} else if (tmp->get_type()==expr_type::ast_callv) {
|
||||||
mcall_vec((call_vector*)tmp);
|
mcall_vec((call_vector*)tmp);
|
||||||
|
} else if (tmp->get_type()==expr_type::ast_callf) {
|
||||||
|
die("bad left-value: function call", tmp->get_location());
|
||||||
|
} else {
|
||||||
|
die("bad left-value: unknown call", tmp->get_location());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,13 +380,22 @@ void codegen::mcall_id(identifier* node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void codegen::mcall_vec(call_vector* node) {
|
void codegen::mcall_vec(call_vector* node) {
|
||||||
calc_gen(node->get_slices()[0]);
|
if (node->get_slices().size()>1) {
|
||||||
|
die("bad left-value: subvec call", node->get_location());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto call = node->get_slices()[0];
|
||||||
|
if (call->get_end()) {
|
||||||
|
die("bad left-value: subvec call", node->get_location());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
calc_gen(call->get_begin());
|
||||||
gen(op_mcallv, 0, node->get_location().begin_line);
|
gen(op_mcallv, 0, node->get_location().begin_line);
|
||||||
}
|
}
|
||||||
|
|
||||||
void codegen::mcall_hash(call_hash* node) {
|
void codegen::mcall_hash(call_hash* node) {
|
||||||
regist_str(node->get_field());
|
regist_str(node->get_field());
|
||||||
gen(op_mcallh, str_table[node->get_field()], node->get_location().begin_line);
|
gen(op_mcallh, str_table.at(node->get_field()), node->get_location().begin_line);
|
||||||
}
|
}
|
||||||
|
|
||||||
void codegen::single_def(definition_expr* node) {
|
void codegen::single_def(definition_expr* node) {
|
||||||
|
@ -396,30 +407,30 @@ void codegen::single_def(definition_expr* node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void codegen::multi_def(definition_expr* node) {
|
void codegen::multi_def(definition_expr* node) {
|
||||||
auto& ids = node->get_variables()->get_variables();
|
auto& identifiers = node->get_variables()->get_variables();
|
||||||
usize size = ids.size();
|
usize size = identifiers.size();
|
||||||
if (node->get_value()->get_type()==expr_type::ast_tuple) { // (var a,b,c)=(c,b,a);
|
if (node->get_value()->get_type()==expr_type::ast_tuple) { // (var a,b,c)=(c,b,a);
|
||||||
auto& vals = ((tuple_expr*)node->get_value())->get_elements();
|
auto& vals = ((tuple_expr*)node->get_value())->get_elements();
|
||||||
if (ids.size()<vals.size()) {
|
if (identifiers.size()<vals.size()) {
|
||||||
die("lack values in multi-definition", node->get_value()->get_location());
|
die("lack values in multi-definition", node->get_value()->get_location());
|
||||||
} else if (ids.size()>vals.size()) {
|
} else if (identifiers.size()>vals.size()) {
|
||||||
die("too many values in multi-definition", node->get_value()->get_location());
|
die("too many values in multi-definition", node->get_value()->get_location());
|
||||||
}
|
}
|
||||||
for(usize i = 0; i<size; ++i) {
|
for(usize i = 0; i<size; ++i) {
|
||||||
calc_gen(vals[i]);
|
calc_gen(vals[i]);
|
||||||
const auto& str = ids[i]->get_name();
|
const auto& name = identifiers[i]->get_name();
|
||||||
local.empty()?
|
local.empty()?
|
||||||
gen(op_loadg, global_find(str), ids[i]->get_location().begin_line):
|
gen(op_loadg, global_find(name), identifiers[i]->get_location().begin_line):
|
||||||
gen(op_loadl, local_find(str), ids[i]->get_location().begin_line);
|
gen(op_loadl, local_find(name), identifiers[i]->get_location().begin_line);
|
||||||
}
|
}
|
||||||
} else { // (var a,b,c)=[0,1,2];
|
} else { // (var a,b,c)=[0,1,2];
|
||||||
calc_gen(node->get_value());
|
calc_gen(node->get_value());
|
||||||
for(usize i = 0; i<size; ++i) {
|
for(usize i = 0; i<size; ++i) {
|
||||||
gen(op_callvi, i, node->get_value()->get_location().begin_line);
|
gen(op_callvi, i, node->get_value()->get_location().begin_line);
|
||||||
const auto& str = ids[i]->get_name();
|
const auto& name = identifiers[i]->get_name();
|
||||||
local.empty()?
|
local.empty()?
|
||||||
gen(op_loadg, global_find(str), ids[i]->get_location().begin_line):
|
gen(op_loadg, global_find(name), identifiers[i]->get_location().begin_line):
|
||||||
gen(op_loadl, local_find(str), ids[i]->get_location().begin_line);
|
gen(op_loadl, local_find(name), identifiers[i]->get_location().begin_line);
|
||||||
}
|
}
|
||||||
gen(op_pop, 0, node->get_location().begin_line);
|
gen(op_pop, 0, node->get_location().begin_line);
|
||||||
}
|
}
|
||||||
|
@ -437,14 +448,14 @@ void codegen::assignment_gen(assignment_expr* node) {
|
||||||
switch(node->get_assignment_type()) {
|
switch(node->get_assignment_type()) {
|
||||||
case assignment_expr::assign_type::equal:
|
case assignment_expr::assign_type::equal:
|
||||||
calc_gen(node->get_right());
|
calc_gen(node->get_right());
|
||||||
mcall((call_expr*)node->get_left());
|
mcall(node->get_left());
|
||||||
gen(op_meq, 0, node->get_location().begin_line);
|
gen(op_meq, 0, node->get_location().begin_line);
|
||||||
break;
|
break;
|
||||||
case assignment_expr::assign_type::add_equal:
|
case assignment_expr::assign_type::add_equal:
|
||||||
if (node->get_right()->get_type()!=expr_type::ast_num) {
|
if (node->get_right()->get_type()!=expr_type::ast_num) {
|
||||||
calc_gen(node->get_right());
|
calc_gen(node->get_right());
|
||||||
}
|
}
|
||||||
mcall((call_expr*)node->get_left());
|
mcall(node->get_left());
|
||||||
if (node->get_right()->get_type()!=expr_type::ast_num) {
|
if (node->get_right()->get_type()!=expr_type::ast_num) {
|
||||||
gen(op_addeq, 0, node->get_location().begin_line);
|
gen(op_addeq, 0, node->get_location().begin_line);
|
||||||
} else {
|
} else {
|
||||||
|
@ -457,7 +468,7 @@ void codegen::assignment_gen(assignment_expr* node) {
|
||||||
if (node->get_right()->get_type()!=expr_type::ast_num) {
|
if (node->get_right()->get_type()!=expr_type::ast_num) {
|
||||||
calc_gen(node->get_right());
|
calc_gen(node->get_right());
|
||||||
}
|
}
|
||||||
mcall((call_expr*)node->get_left());
|
mcall(node->get_left());
|
||||||
if (node->get_right()->get_type()!=expr_type::ast_num) {
|
if (node->get_right()->get_type()!=expr_type::ast_num) {
|
||||||
gen(op_subeq, 0, node->get_location().begin_line);
|
gen(op_subeq, 0, node->get_location().begin_line);
|
||||||
} else {
|
} else {
|
||||||
|
@ -470,7 +481,7 @@ void codegen::assignment_gen(assignment_expr* node) {
|
||||||
if (node->get_right()->get_type()!=expr_type::ast_num) {
|
if (node->get_right()->get_type()!=expr_type::ast_num) {
|
||||||
calc_gen(node->get_right());
|
calc_gen(node->get_right());
|
||||||
}
|
}
|
||||||
mcall((call_expr*)node->get_left());
|
mcall(node->get_left());
|
||||||
if (node->get_right()->get_type()!=expr_type::ast_num) {
|
if (node->get_right()->get_type()!=expr_type::ast_num) {
|
||||||
gen(op_muleq, 0, node->get_location().begin_line);
|
gen(op_muleq, 0, node->get_location().begin_line);
|
||||||
} else {
|
} else {
|
||||||
|
@ -483,7 +494,7 @@ void codegen::assignment_gen(assignment_expr* node) {
|
||||||
if (node->get_right()->get_type()!=expr_type::ast_num) {
|
if (node->get_right()->get_type()!=expr_type::ast_num) {
|
||||||
calc_gen(node->get_right());
|
calc_gen(node->get_right());
|
||||||
}
|
}
|
||||||
mcall((call_expr*)node->get_left());
|
mcall(node->get_left());
|
||||||
if (node->get_right()->get_type()!=expr_type::ast_num) {
|
if (node->get_right()->get_type()!=expr_type::ast_num) {
|
||||||
gen(op_diveq, 0, node->get_location().begin_line);
|
gen(op_diveq, 0, node->get_location().begin_line);
|
||||||
} else {
|
} else {
|
||||||
|
@ -496,7 +507,7 @@ void codegen::assignment_gen(assignment_expr* node) {
|
||||||
if (node->get_right()->get_type()!=expr_type::ast_str) {
|
if (node->get_right()->get_type()!=expr_type::ast_str) {
|
||||||
calc_gen(node->get_right());
|
calc_gen(node->get_right());
|
||||||
}
|
}
|
||||||
mcall((call_expr*)node->get_left());
|
mcall(node->get_left());
|
||||||
if (node->get_right()->get_type()!=expr_type::ast_str) {
|
if (node->get_right()->get_type()!=expr_type::ast_str) {
|
||||||
gen(op_lnkeq, 0, node->get_location().begin_line);
|
gen(op_lnkeq, 0, node->get_location().begin_line);
|
||||||
} else {
|
} else {
|
||||||
|
@ -507,17 +518,17 @@ void codegen::assignment_gen(assignment_expr* node) {
|
||||||
break;
|
break;
|
||||||
case assignment_expr::assign_type::bitwise_and_equal:
|
case assignment_expr::assign_type::bitwise_and_equal:
|
||||||
calc_gen(node->get_right());
|
calc_gen(node->get_right());
|
||||||
mcall((call_expr*)node->get_left());
|
mcall(node->get_left());
|
||||||
gen(op_btandeq, 0, node->get_location().begin_line);
|
gen(op_btandeq, 0, node->get_location().begin_line);
|
||||||
break;
|
break;
|
||||||
case assignment_expr::assign_type::bitwise_or_equal:
|
case assignment_expr::assign_type::bitwise_or_equal:
|
||||||
calc_gen(node->get_right());
|
calc_gen(node->get_right());
|
||||||
mcall((call_expr*)node->get_left());
|
mcall(node->get_left());
|
||||||
gen(op_btoreq, 0, node->get_location().begin_line);
|
gen(op_btoreq, 0, node->get_location().begin_line);
|
||||||
break;
|
break;
|
||||||
case assignment_expr::assign_type::bitwise_xor_equal:
|
case assignment_expr::assign_type::bitwise_xor_equal:
|
||||||
calc_gen(node->get_right());
|
calc_gen(node->get_right());
|
||||||
mcall((call_expr*)node->get_left());
|
mcall(node->get_left());
|
||||||
gen(op_btxoreq, 0, node->get_location().begin_line);
|
gen(op_btxoreq, 0, node->get_location().begin_line);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -581,7 +592,7 @@ void codegen::multi_assign_gen(multi_assign* node) {
|
||||||
}
|
}
|
||||||
auto& tuple = node->get_tuple()->get_elements();
|
auto& tuple = node->get_tuple()->get_elements();
|
||||||
for(i32 i = 0; i<size; ++i) {
|
for(i32 i = 0; i<size; ++i) {
|
||||||
mcall((call_expr*)tuple[i]);
|
mcall(tuple[i]);
|
||||||
// multi assign user loadl and loadg to avoid meq's stack--
|
// multi assign user loadl and loadg to avoid meq's stack--
|
||||||
// and this operation changes local and global value directly
|
// and this operation changes local and global value directly
|
||||||
if (code.back().op==op_mcalll) {
|
if (code.back().op==op_mcalll) {
|
||||||
|
@ -601,7 +612,7 @@ void codegen::multi_assign_gen(multi_assign* node) {
|
||||||
gen(op_callvi, i, node->get_value()->get_location().begin_line);
|
gen(op_callvi, i, node->get_value()->get_location().begin_line);
|
||||||
// multi assign user loadl and loadg to avoid meq's stack--
|
// multi assign user loadl and loadg to avoid meq's stack--
|
||||||
// and this operation changes local and global value directly
|
// and this operation changes local and global value directly
|
||||||
mcall((call_expr*)tuple[i]);
|
mcall(tuple[i]);
|
||||||
if (code.back().op==op_mcalll) {
|
if (code.back().op==op_mcalll) {
|
||||||
code.back().op=op_loadl;
|
code.back().op=op_loadl;
|
||||||
} else if (code.back().op==op_mupval) {
|
} else if (code.back().op==op_mupval) {
|
||||||
|
@ -622,6 +633,11 @@ void codegen::cond_gen(condition_expr* node) {
|
||||||
auto ptr = code.size();
|
auto ptr = code.size();
|
||||||
gen(op_jf, 0, node->get_if_statement()->get_location().begin_line);
|
gen(op_jf, 0, node->get_if_statement()->get_location().begin_line);
|
||||||
block_gen(node->get_if_statement()->get_code_block());
|
block_gen(node->get_if_statement()->get_code_block());
|
||||||
|
if (node->get_elsif_stataments().size() ||
|
||||||
|
node->get_else_statement()) {
|
||||||
|
jmp_label.push_back(code.size());
|
||||||
|
gen(op_jmp, 0, node->get_if_statement()->get_location().begin_line);
|
||||||
|
}
|
||||||
code[ptr].num = code.size();
|
code[ptr].num = code.size();
|
||||||
|
|
||||||
for(auto tmp : node->get_elsif_stataments()) {
|
for(auto tmp : node->get_elsif_stataments()) {
|
||||||
|
@ -655,7 +671,8 @@ void codegen::loop_gen(expr* node) {
|
||||||
case expr_type::ast_for:
|
case expr_type::ast_for:
|
||||||
for_gen((for_expr*)node); break;
|
for_gen((for_expr*)node); break;
|
||||||
case expr_type::ast_forei:
|
case expr_type::ast_forei:
|
||||||
if (((forei_expr*)node)->get_loop_type()==forei_expr::forei_loop_type::forindex) {
|
if (((forei_expr*)node)->get_loop_type()==
|
||||||
|
forei_expr::forei_loop_type::forindex) {
|
||||||
forindex_gen((forei_expr*)node);
|
forindex_gen((forei_expr*)node);
|
||||||
} else {
|
} else {
|
||||||
foreach_gen((forei_expr*)node);
|
foreach_gen((forei_expr*)node);
|
||||||
|
@ -691,7 +708,8 @@ void codegen::for_gen(for_expr* node) {
|
||||||
expr_gen(node->get_initial());
|
expr_gen(node->get_initial());
|
||||||
usize jmp_place = code.size();
|
usize jmp_place = code.size();
|
||||||
if (node->get_condition()->get_type()==expr_type::ast_null) {
|
if (node->get_condition()->get_type()==expr_type::ast_null) {
|
||||||
gen(op_pnum, num_table[1], node->get_condition()->get_location().begin_line);
|
regist_num(1);
|
||||||
|
gen(op_pnum, num_table.at(1), node->get_condition()->get_location().begin_line);
|
||||||
} else {
|
} else {
|
||||||
calc_gen(node->get_condition());
|
calc_gen(node->get_condition());
|
||||||
}
|
}
|
||||||
|
@ -714,6 +732,8 @@ void codegen::expr_gen(expr* node) {
|
||||||
def_gen((definition_expr*)node); break;
|
def_gen((definition_expr*)node); break;
|
||||||
case expr_type::ast_multi_assign:
|
case expr_type::ast_multi_assign:
|
||||||
multi_assign_gen((multi_assign*)node); break;
|
multi_assign_gen((multi_assign*)node); break;
|
||||||
|
case expr_type::ast_assign:
|
||||||
|
assign_statement((assignment_expr*)node); break;
|
||||||
case expr_type::ast_nil:case expr_type::ast_num:
|
case expr_type::ast_nil:case expr_type::ast_num:
|
||||||
case expr_type::ast_str:case expr_type::ast_bool:break;
|
case expr_type::ast_str:case expr_type::ast_bool:break;
|
||||||
case expr_type::ast_vec:case expr_type::ast_hash:
|
case expr_type::ast_vec:case expr_type::ast_hash:
|
||||||
|
@ -724,8 +744,6 @@ void codegen::expr_gen(expr* node) {
|
||||||
calc_gen(node);
|
calc_gen(node);
|
||||||
gen(op_pop, 0, node->get_location().begin_line);
|
gen(op_pop, 0, node->get_location().begin_line);
|
||||||
break;
|
break;
|
||||||
case expr_type::ast_assign:
|
|
||||||
assign_statement((assignment_expr*)node); break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -740,7 +758,7 @@ void codegen::forindex_gen(forei_expr* node) {
|
||||||
gen(op_loadg, global_find(str), node->get_iterator()->get_name()->get_location().begin_line):
|
gen(op_loadg, global_find(str), node->get_iterator()->get_name()->get_location().begin_line):
|
||||||
gen(op_loadl, local_find(str), node->get_iterator()->get_name()->get_location().begin_line);
|
gen(op_loadl, local_find(str), node->get_iterator()->get_name()->get_location().begin_line);
|
||||||
} else { // use exist variable as the iterator
|
} else { // use exist variable as the iterator
|
||||||
mcall((call_expr*)node->get_iterator()->get_call());
|
mcall(node->get_iterator()->get_call());
|
||||||
if (code.back().op==op_mcallg) {
|
if (code.back().op==op_mcallg) {
|
||||||
code.back().op=op_loadg;
|
code.back().op=op_loadg;
|
||||||
} else if (code.back().op==op_mcalll) {
|
} else if (code.back().op==op_mcalll) {
|
||||||
|
@ -772,7 +790,7 @@ void codegen::foreach_gen(forei_expr* node) {
|
||||||
gen(op_loadg, global_find(str), node->get_iterator()->get_name()->get_location().begin_line):
|
gen(op_loadg, global_find(str), node->get_iterator()->get_name()->get_location().begin_line):
|
||||||
gen(op_loadl, local_find(str), node->get_iterator()->get_name()->get_location().begin_line);
|
gen(op_loadl, local_find(str), node->get_iterator()->get_name()->get_location().begin_line);
|
||||||
} else { // use exist variable as the iterator
|
} else { // use exist variable as the iterator
|
||||||
mcall((call_expr*)node->get_iterator()->get_call());
|
mcall(node->get_iterator()->get_call());
|
||||||
if (code.back().op==op_mcallg) {
|
if (code.back().op==op_mcallg) {
|
||||||
code.back().op=op_loadg;
|
code.back().op=op_loadg;
|
||||||
} else if (code.back().op==op_mcalll) {
|
} else if (code.back().op==op_mcalll) {
|
||||||
|
@ -881,7 +899,7 @@ void codegen::binary_gen(binary_operator* node) {
|
||||||
} else {
|
} else {
|
||||||
auto num = ((number_literal*)node->get_right())->get_number();
|
auto num = ((number_literal*)node->get_right())->get_number();
|
||||||
regist_num(num);
|
regist_num(num);
|
||||||
gen(op_addc, num_table[num], node->get_location().begin_line);
|
gen(op_addc, num_table.at(num), node->get_location().begin_line);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case binary_operator::binary_type::sub:
|
case binary_operator::binary_type::sub:
|
||||||
|
@ -892,7 +910,7 @@ void codegen::binary_gen(binary_operator* node) {
|
||||||
} else {
|
} else {
|
||||||
auto num = ((number_literal*)node->get_right())->get_number();
|
auto num = ((number_literal*)node->get_right())->get_number();
|
||||||
regist_num(num);
|
regist_num(num);
|
||||||
gen(op_subc, num_table[num], node->get_location().begin_line);
|
gen(op_subc, num_table.at(num), node->get_location().begin_line);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case binary_operator::binary_type::mult:
|
case binary_operator::binary_type::mult:
|
||||||
|
@ -903,7 +921,7 @@ void codegen::binary_gen(binary_operator* node) {
|
||||||
} else {
|
} else {
|
||||||
auto num = ((number_literal*)node->get_right())->get_number();
|
auto num = ((number_literal*)node->get_right())->get_number();
|
||||||
regist_num(num);
|
regist_num(num);
|
||||||
gen(op_mulc, num_table[num], node->get_location().begin_line);
|
gen(op_mulc, num_table.at(num), node->get_location().begin_line);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case binary_operator::binary_type::div:
|
case binary_operator::binary_type::div:
|
||||||
|
@ -914,7 +932,7 @@ void codegen::binary_gen(binary_operator* node) {
|
||||||
} else {
|
} else {
|
||||||
auto num = ((number_literal*)node->get_right())->get_number();
|
auto num = ((number_literal*)node->get_right())->get_number();
|
||||||
regist_num(num);
|
regist_num(num);
|
||||||
gen(op_divc, num_table[num], node->get_location().begin_line);
|
gen(op_divc, num_table.at(num), node->get_location().begin_line);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case binary_operator::binary_type::concat:
|
case binary_operator::binary_type::concat:
|
||||||
|
@ -925,10 +943,9 @@ void codegen::binary_gen(binary_operator* node) {
|
||||||
} else {
|
} else {
|
||||||
const auto& str = ((string_literal*)node->get_right())->get_content();
|
const auto& str = ((string_literal*)node->get_right())->get_content();
|
||||||
regist_str(str);
|
regist_str(str);
|
||||||
gen(op_lnkc, str_table[str], node->get_location().begin_line);
|
gen(op_lnkc, str_table.at(str), node->get_location().begin_line);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
// ast_cmpeq(27)~ast_geq(32) op_eq(29)~op_geq(34)
|
|
||||||
case binary_operator::binary_type::less:
|
case binary_operator::binary_type::less:
|
||||||
calc_gen(node->get_left());
|
calc_gen(node->get_left());
|
||||||
if (node->get_right()->get_type()!=expr_type::ast_num) {
|
if (node->get_right()->get_type()!=expr_type::ast_num) {
|
||||||
|
@ -937,7 +954,7 @@ void codegen::binary_gen(binary_operator* node) {
|
||||||
} else {
|
} else {
|
||||||
auto num = ((number_literal*)node->get_right())->get_number();
|
auto num = ((number_literal*)node->get_right())->get_number();
|
||||||
regist_num(num);
|
regist_num(num);
|
||||||
gen(op_lessc, num_table[num], node->get_location().begin_line);
|
gen(op_lessc, num_table.at(num), node->get_location().begin_line);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case binary_operator::binary_type::leq:
|
case binary_operator::binary_type::leq:
|
||||||
|
@ -948,7 +965,7 @@ void codegen::binary_gen(binary_operator* node) {
|
||||||
} else {
|
} else {
|
||||||
auto num = ((number_literal*)node->get_right())->get_number();
|
auto num = ((number_literal*)node->get_right())->get_number();
|
||||||
regist_num(num);
|
regist_num(num);
|
||||||
gen(op_leqc, num_table[num], node->get_location().begin_line);
|
gen(op_leqc, num_table.at(num), node->get_location().begin_line);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case binary_operator::binary_type::grt:
|
case binary_operator::binary_type::grt:
|
||||||
|
@ -959,7 +976,7 @@ void codegen::binary_gen(binary_operator* node) {
|
||||||
} else {
|
} else {
|
||||||
auto num = ((number_literal*)node->get_right())->get_number();
|
auto num = ((number_literal*)node->get_right())->get_number();
|
||||||
regist_num(num);
|
regist_num(num);
|
||||||
gen(op_grtc, num_table[num], node->get_location().begin_line);
|
gen(op_grtc, num_table.at(num), node->get_location().begin_line);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case binary_operator::binary_type::geq:
|
case binary_operator::binary_type::geq:
|
||||||
|
@ -970,7 +987,7 @@ void codegen::binary_gen(binary_operator* node) {
|
||||||
} else {
|
} else {
|
||||||
auto num = ((number_literal*)node->get_right())->get_number();
|
auto num = ((number_literal*)node->get_right())->get_number();
|
||||||
regist_num(num);
|
regist_num(num);
|
||||||
gen(op_geqc, num_table[num], node->get_location().begin_line);
|
gen(op_geqc, num_table.at(num), node->get_location().begin_line);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1017,6 +1034,7 @@ void codegen::calc_gen(expr* node) {
|
||||||
case expr_type::ast_binary:
|
case expr_type::ast_binary:
|
||||||
binary_gen((binary_operator*)node); break;
|
binary_gen((binary_operator*)node); break;
|
||||||
case expr_type::ast_def:
|
case expr_type::ast_def:
|
||||||
|
// definition in calculation only should be single def
|
||||||
single_def((definition_expr*)node);
|
single_def((definition_expr*)node);
|
||||||
call_id(((definition_expr*)node)->get_variable_name());
|
call_id(((definition_expr*)node)->get_variable_name());
|
||||||
break;
|
break;
|
||||||
|
@ -1053,6 +1071,7 @@ void codegen::block_gen(code_block* node) {
|
||||||
case expr_type::ast_unary:
|
case expr_type::ast_unary:
|
||||||
case expr_type::ast_ternary:
|
case expr_type::ast_ternary:
|
||||||
case expr_type::ast_def:
|
case expr_type::ast_def:
|
||||||
|
case expr_type::ast_assign:
|
||||||
case expr_type::ast_multi_assign:
|
case expr_type::ast_multi_assign:
|
||||||
expr_gen(tmp); break;
|
expr_gen(tmp); break;
|
||||||
case expr_type::ast_ret:
|
case expr_type::ast_ret:
|
||||||
|
@ -1105,12 +1124,12 @@ void codegen::print() {
|
||||||
std::stack<u32> festk;
|
std::stack<u32> festk;
|
||||||
|
|
||||||
// print const numbers
|
// print const numbers
|
||||||
for(auto& num:num_res) {
|
for(auto num : num_res) {
|
||||||
std::cout<<" .number "<<num<<"\n";
|
std::cout<<" .number "<<num<<"\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
// print const strings
|
// print const strings
|
||||||
for(auto& str:str_res) {
|
for(const auto& str : str_res) {
|
||||||
std::cout<<" .symbol \""<<rawstr(str)<<"\"\n";
|
std::cout<<" .symbol \""<<rawstr(str)<<"\"\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1119,7 +1138,7 @@ void codegen::print() {
|
||||||
codestream::set(num_res.data(), str_res.data());
|
codestream::set(num_res.data(), str_res.data());
|
||||||
for(u32 i = 0; i<code.size(); ++i) {
|
for(u32 i = 0; i<code.size(); ++i) {
|
||||||
// print opcode index, opcode name, opcode immediate number
|
// print opcode index, opcode name, opcode immediate number
|
||||||
const opcode& c=code[i];
|
const auto& c = code[i];
|
||||||
if (!festk.empty() && i==festk.top()) {
|
if (!festk.empty() && i==festk.top()) {
|
||||||
std::cout<<std::hex<<"<0x"<<fbstk.top()<<std::dec<<">;\n";
|
std::cout<<std::hex<<"<0x"<<fbstk.top()<<std::dec<<">;\n";
|
||||||
// avoid two empty lines
|
// avoid two empty lines
|
||||||
|
|
|
@ -40,7 +40,6 @@ private:
|
||||||
// but in fact local scope also has less than STACK_DEPTH value
|
// but in fact local scope also has less than STACK_DEPTH value
|
||||||
std::list<std::unordered_map<std::string,i32>> local;
|
std::list<std::unordered_map<std::string,i32>> local;
|
||||||
|
|
||||||
bool check_memory_reachable(expr*);
|
|
||||||
void check_id_exist(identifier*);
|
void check_id_exist(identifier*);
|
||||||
|
|
||||||
void die(const std::string& info, const span& loc) {
|
void die(const std::string& info, const span& loc) {
|
||||||
|
@ -68,7 +67,7 @@ private:
|
||||||
void call_hash_gen(call_hash*);
|
void call_hash_gen(call_hash*);
|
||||||
void call_vec(call_vector*);
|
void call_vec(call_vector*);
|
||||||
void call_func(call_function*);
|
void call_func(call_function*);
|
||||||
void mcall(call_expr*);
|
void mcall(expr*);
|
||||||
void mcall_id(identifier*);
|
void mcall_id(identifier*);
|
||||||
void mcall_vec(call_vector*);
|
void mcall_vec(call_vector*);
|
||||||
void mcall_hash(call_hash*);
|
void mcall_hash(call_hash*);
|
||||||
|
|
|
@ -939,14 +939,21 @@ iter_expr* parse::iter_gen() {
|
||||||
if (lookahead(tok::var)) {
|
if (lookahead(tok::var)) {
|
||||||
match(tok::var);
|
match(tok::var);
|
||||||
node->set_name(id());
|
node->set_name(id());
|
||||||
} else {
|
update_location(node);
|
||||||
auto tmp = new call_expr(toks[ptr].loc);
|
return node;
|
||||||
|
}
|
||||||
|
auto id_node = id();
|
||||||
|
if (!is_call(toks[ptr].type)) {
|
||||||
|
node->set_name(id_node);
|
||||||
|
update_location(node);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
auto tmp = new call_expr(id_node->get_location());
|
||||||
tmp->set_first(id());
|
tmp->set_first(id());
|
||||||
while(is_call(toks[ptr].type)) {
|
while(is_call(toks[ptr].type)) {
|
||||||
tmp->add_call(call_scalar());
|
tmp->add_call(call_scalar());
|
||||||
}
|
}
|
||||||
node->set_call(tmp);
|
node->set_call(tmp);
|
||||||
}
|
|
||||||
update_location(node);
|
update_location(node);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,6 +144,7 @@ protected:
|
||||||
void o_mcallv();
|
void o_mcallv();
|
||||||
void o_mcallh();
|
void o_mcallh();
|
||||||
void o_ret();
|
void o_ret();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/* constructor of vm instance */
|
/* constructor of vm instance */
|
||||||
|
|
Loading…
Reference in New Issue