3008 lines
93 KiB
C++
3008 lines
93 KiB
C++
// Copyright 2021 xensik. All rights reserved.
|
|
//
|
|
// Use of this source code is governed by a GNU GPLv3 license
|
|
// that can be found in the LICENSE file.
|
|
|
|
#include "stdafx.hpp"
|
|
#include "iw5.hpp"
|
|
#include "parser.hpp"
|
|
#include "lexer.hpp"
|
|
|
|
namespace xsk::gsc::iw5
|
|
{
|
|
|
|
auto compiler::output() -> std::vector<gsc::function_ptr>
|
|
{
|
|
return std::move(assembly_);
|
|
}
|
|
|
|
void compiler::compile(const std::string& file, std::vector<std::uint8_t>& data)
|
|
{
|
|
filename_ = file;
|
|
|
|
auto result = parse_buffer(filename_, data);
|
|
|
|
compile_program(result);
|
|
}
|
|
|
|
void compiler::set_readf_callback(std::function<std::vector<std::uint8_t>(const std::string&)> func)
|
|
{
|
|
callback_readf_ = func;
|
|
}
|
|
|
|
auto compiler::parse_buffer(const std::string& file, std::vector<std::uint8_t>& data) -> gsc::program_ptr
|
|
{
|
|
yyscan_t scanner;
|
|
gsc::location loc;
|
|
gsc::program_ptr result(nullptr);
|
|
|
|
loc.initialize(&file);
|
|
// Add the two NULL terminators, required by flex.
|
|
data.push_back(0);
|
|
data.push_back(0);
|
|
|
|
if (iw5_lex_init(&scanner))
|
|
exit(1);
|
|
|
|
YY_BUFFER_STATE yybuffer = iw5__scan_buffer(reinterpret_cast<char*>(data.data()), data.size(), scanner);
|
|
|
|
parser parser(scanner, loc, result);
|
|
|
|
if(parser.parse() || result == nullptr)
|
|
{
|
|
throw gsc::comp_error(loc, "An unknown error ocurred while parsing gsc file.");
|
|
}
|
|
|
|
iw5__delete_buffer(yybuffer, scanner);
|
|
iw5_lex_destroy(scanner);
|
|
|
|
return result;
|
|
}
|
|
|
|
auto compiler::parse_file(const std::string& file) -> gsc::program_ptr
|
|
{
|
|
auto buffer = callback_readf_(file);
|
|
auto result = parse_buffer(file, buffer);
|
|
|
|
return result;
|
|
}
|
|
|
|
void compiler::compile_program(const gsc::program_ptr& program)
|
|
{
|
|
assembly_.clear();
|
|
includes_.clear();
|
|
animtrees_.clear();
|
|
constants_.clear();
|
|
local_functions_.clear();
|
|
index_ = 1;
|
|
|
|
for(const auto& def : program->definitions)
|
|
{
|
|
if(def.as_node->type == gsc::node_t::thread)
|
|
{
|
|
local_functions_.push_back(def.as_thread->name->value);
|
|
}
|
|
}
|
|
|
|
for(const auto& include : program->includes)
|
|
{
|
|
emit_include(include);
|
|
}
|
|
|
|
for(const auto& def : program->definitions)
|
|
{
|
|
emit_define(def);
|
|
}
|
|
|
|
#ifdef DEBUG_GSC_COMPILER
|
|
print_debug_info();
|
|
#endif
|
|
}
|
|
|
|
void compiler::emit_include(const gsc::include_ptr& include)
|
|
{
|
|
const auto& path = include->file->value;
|
|
|
|
for(const auto& inc : includes_)
|
|
{
|
|
if(inc.name == path)
|
|
{
|
|
throw gsc::comp_error(include->loc, "error duplicated include file '" + path + "'.");
|
|
}
|
|
}
|
|
|
|
if(map_known_includes(path)) return;
|
|
|
|
try
|
|
{
|
|
auto program = parse_file(path);
|
|
|
|
std::vector<std::string> funcs;
|
|
|
|
for(const auto& def : program->definitions)
|
|
{
|
|
if(def.as_node->type == gsc::node_t::thread)
|
|
{
|
|
funcs.push_back(def.as_thread->name->value);
|
|
}
|
|
}
|
|
|
|
if(funcs.size() == 0)
|
|
{
|
|
throw gsc::comp_error(include->loc, "error empty include file '" + path + "'.");
|
|
}
|
|
|
|
includes_.push_back(include_t(path, funcs));
|
|
}
|
|
catch(const std::exception& e)
|
|
{
|
|
throw gsc::comp_error(include->loc, "error parsing include file '" + path + "': " + e.what());
|
|
}
|
|
}
|
|
|
|
void compiler::emit_define(const gsc::define_ptr& define)
|
|
{
|
|
switch(define.as_node->type)
|
|
{
|
|
case gsc::node_t::usingtree: emit_usingtree(define.as_usingtree); break;
|
|
case gsc::node_t::constant: emit_constant(define.as_constant); break;
|
|
case gsc::node_t::thread: emit_thread(define.as_thread); break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
void compiler::emit_usingtree(const gsc::usingtree_ptr& animtree)
|
|
{
|
|
animtrees_.push_back({ animtree->animtree->value, false });
|
|
}
|
|
|
|
void compiler::emit_constant(const gsc::constant_ptr& constant)
|
|
{
|
|
constants_.insert({ constant->name->value, std::move(constant->value) });
|
|
}
|
|
|
|
void compiler::emit_thread(const gsc::thread_ptr& thread)
|
|
{
|
|
function_ = std::make_unique<gsc::function>();
|
|
function_->index = index_;
|
|
function_->name = thread->name->value;
|
|
|
|
auto ctx = std::make_unique<gsc::context>();
|
|
stack_idx_ = 0;
|
|
label_idx_ = 0;
|
|
can_break_ = false;
|
|
can_continue_ = false;
|
|
local_stack_.clear();
|
|
break_ctxs_.clear();
|
|
continue_ctxs_.clear();
|
|
|
|
process_thread(ctx, thread);
|
|
|
|
emit_parameters(ctx, thread->params);
|
|
emit_stmt_list(ctx, thread->block, true);
|
|
emit_opcode(ctx, opcode::OP_End);
|
|
|
|
function_->size = index_ - function_->index;
|
|
assembly_.push_back(std::move(function_));
|
|
}
|
|
|
|
void compiler::emit_parameters(const gsc::context_ptr& ctx, const gsc::parameters_ptr& params)
|
|
{
|
|
for(const auto& param : params->list)
|
|
{
|
|
initialize_variable(ctx, param);
|
|
emit_opcode(ctx, opcode::OP_SafeCreateVariableFieldCached, variable_create_index(ctx, param));
|
|
}
|
|
|
|
emit_opcode(ctx, opcode::OP_checkclearparams);
|
|
}
|
|
|
|
void compiler::emit_stmt(const gsc::context_ptr& ctx, const gsc::stmt_ptr& stmt, bool last)
|
|
{
|
|
switch(stmt.as_node->type)
|
|
{
|
|
case gsc::node_t::stmt_list: emit_stmt_list(ctx, stmt.as_list, last); break;
|
|
case gsc::node_t::stmt_call: emit_stmt_call(ctx, stmt.as_call); break;
|
|
case gsc::node_t::stmt_assign: emit_stmt_assign(ctx, stmt.as_assign); break;
|
|
case gsc::node_t::stmt_endon: emit_stmt_endon(ctx, stmt.as_endon); break;
|
|
case gsc::node_t::stmt_notify: emit_stmt_notify(ctx, stmt.as_notify); break;
|
|
case gsc::node_t::stmt_wait: emit_stmt_wait(ctx, stmt.as_wait); break;
|
|
case gsc::node_t::stmt_waittill: emit_stmt_waittill(ctx, stmt.as_waittill); break;
|
|
case gsc::node_t::stmt_waittillmatch: emit_stmt_waittillmatch(ctx, stmt.as_waittillmatch); break;
|
|
case gsc::node_t::stmt_waittillframeend: emit_stmt_waittillframeend(ctx, stmt.as_waittillframeend); break;
|
|
case gsc::node_t::stmt_if: emit_stmt_if(ctx, stmt.as_if, last); break;
|
|
case gsc::node_t::stmt_ifelse: emit_stmt_ifelse(ctx, stmt.as_ifelse, last); break;
|
|
case gsc::node_t::stmt_while: emit_stmt_while(ctx, stmt.as_while); break;
|
|
case gsc::node_t::stmt_for: emit_stmt_for(ctx, stmt.as_for); break;
|
|
case gsc::node_t::stmt_foreach: emit_stmt_foreach(ctx, stmt.as_foreach); break;
|
|
case gsc::node_t::stmt_switch: emit_stmt_switch(ctx, stmt.as_switch); break;
|
|
case gsc::node_t::stmt_case: emit_stmt_case(ctx, stmt.as_case); break;
|
|
case gsc::node_t::stmt_default: emit_stmt_default(ctx, stmt.as_default); break;
|
|
case gsc::node_t::stmt_break: emit_stmt_break(ctx, stmt.as_break); break;
|
|
case gsc::node_t::stmt_continue: emit_stmt_continue(ctx, stmt.as_continue); break;
|
|
case gsc::node_t::stmt_return: emit_stmt_return(ctx, stmt.as_return); break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
void compiler::emit_stmt_list(const gsc::context_ptr& ctx, const gsc::stmt_list_ptr& stmt, bool last)
|
|
{
|
|
for (const auto& entry : stmt->stmts)
|
|
{
|
|
bool last_ = (&entry == &stmt->stmts.back() && last) ? true : false;
|
|
emit_stmt(ctx, entry, last_);
|
|
}
|
|
}
|
|
|
|
void compiler::emit_stmt_call(const gsc::context_ptr& ctx, const gsc::stmt_call_ptr& stmt)
|
|
{
|
|
if(stmt->expr->func.as_node->type == gsc::node_t::expr_call_function)
|
|
{
|
|
const auto& name = stmt->expr->func.as_func->name->value;
|
|
|
|
if(name == "assert" || name == "assertex" || name == "assertmsg") return;
|
|
}
|
|
|
|
emit_expr_call(ctx, stmt->expr);
|
|
emit_opcode(ctx, opcode::OP_DecTop);
|
|
}
|
|
|
|
void compiler::emit_stmt_assign(const gsc::context_ptr& ctx, const gsc::stmt_assign_ptr& stmt)
|
|
{
|
|
emit_expr_assign(ctx, stmt->expr);
|
|
}
|
|
|
|
void compiler::emit_stmt_endon(const gsc::context_ptr& ctx, const gsc::stmt_endon_ptr& stmt)
|
|
{
|
|
emit_expr(ctx, stmt->expr);
|
|
emit_expr(ctx, stmt->obj);
|
|
emit_opcode(ctx, opcode::OP_endon);
|
|
}
|
|
|
|
void compiler::emit_stmt_notify(const gsc::context_ptr& ctx, const gsc::stmt_notify_ptr& stmt)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_voidCodepos);
|
|
|
|
std::reverse(stmt->args->list.begin(), stmt->args->list.end());
|
|
|
|
for(const auto& arg : stmt->args->list)
|
|
{
|
|
emit_expr(ctx, arg);
|
|
}
|
|
|
|
emit_expr(ctx, stmt->expr);
|
|
emit_expr(ctx, stmt->obj);
|
|
emit_opcode(ctx, opcode::OP_notify);
|
|
}
|
|
|
|
void compiler::emit_stmt_wait(const gsc::context_ptr& ctx, const gsc::stmt_wait_ptr& stmt)
|
|
{
|
|
emit_expr(ctx, stmt->expr);
|
|
emit_opcode(ctx, opcode::OP_wait);
|
|
}
|
|
|
|
void compiler::emit_stmt_waittill(const gsc::context_ptr& ctx, const gsc::stmt_waittill_ptr& stmt)
|
|
{
|
|
emit_expr(ctx, stmt->expr);
|
|
emit_expr(ctx, stmt->obj);
|
|
emit_opcode(ctx, opcode::OP_waittill);
|
|
|
|
for(const auto& arg : stmt->args->list)
|
|
{
|
|
create_variable(ctx, arg.as_name);
|
|
emit_opcode(ctx, opcode::OP_SafeSetWaittillVariableFieldCached, variable_access_index(ctx, arg.as_name));
|
|
}
|
|
|
|
emit_opcode(ctx, opcode::OP_clearparams);
|
|
}
|
|
|
|
void compiler::emit_stmt_waittillmatch(const gsc::context_ptr& ctx, const gsc::stmt_waittillmatch_ptr& stmt)
|
|
{
|
|
emit_expr_arguments(ctx, stmt->args);
|
|
emit_expr(ctx, stmt->expr);
|
|
emit_expr(ctx, stmt->obj);
|
|
emit_opcode(ctx, opcode::OP_waittillmatch);
|
|
emit_opcode(ctx, opcode::OP_clearparams);
|
|
}
|
|
|
|
void compiler::emit_stmt_waittillframeend(const gsc::context_ptr& ctx, const gsc::stmt_waittillframeend_ptr& stmt)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_waittillFrameEnd);
|
|
}
|
|
|
|
void compiler::emit_stmt_if(const gsc::context_ptr& ctx, const gsc::stmt_if_ptr& stmt, bool last)
|
|
{
|
|
auto end_loc = create_label();
|
|
|
|
if(stmt->expr.as_node->type == gsc::node_t::expr_not)
|
|
{
|
|
emit_expr(ctx, stmt->expr.as_not->rvalue);
|
|
emit_opcode(ctx, opcode::OP_JumpOnTrue, end_loc);
|
|
}
|
|
else
|
|
{
|
|
emit_expr(ctx, stmt->expr);
|
|
emit_opcode(ctx, opcode::OP_JumpOnFalse, end_loc);
|
|
}
|
|
|
|
ctx->transfer(stmt->ctx);
|
|
stmt->ctx->is_last = last;
|
|
|
|
emit_stmt(stmt->ctx, stmt->stmt, last);
|
|
|
|
last ? emit_opcode(ctx, opcode::OP_End) : emit_remove_local_vars(stmt->ctx);
|
|
|
|
insert_label(end_loc);
|
|
}
|
|
|
|
void compiler::emit_stmt_ifelse(const gsc::context_ptr& ctx, const gsc::stmt_ifelse_ptr& stmt, bool last)
|
|
{
|
|
std::vector<gsc::context*> childs;
|
|
auto else_loc = create_label();
|
|
auto end_loc = create_label();
|
|
|
|
if(stmt->expr.as_node->type == gsc::node_t::expr_not)
|
|
{
|
|
emit_expr(ctx, stmt->expr.as_not->rvalue);
|
|
emit_opcode(ctx, opcode::OP_JumpOnTrue, else_loc);
|
|
}
|
|
else
|
|
{
|
|
emit_expr(ctx, stmt->expr);
|
|
emit_opcode(ctx, opcode::OP_JumpOnFalse, else_loc);
|
|
}
|
|
|
|
ctx->transfer(stmt->ctx_if);
|
|
stmt->ctx_if->is_last = last;
|
|
|
|
emit_stmt(stmt->ctx_if, stmt->stmt_if, last);
|
|
|
|
emit_remove_local_vars(stmt->ctx_if);
|
|
|
|
if(stmt->ctx_if->abort == abort_t::abort_none)
|
|
childs.push_back(stmt->ctx_if.get());
|
|
|
|
last ? emit_opcode(ctx, opcode::OP_End) : emit_opcode(ctx, opcode::OP_jump, end_loc);
|
|
|
|
insert_label(else_loc);
|
|
|
|
ctx->transfer(stmt->ctx_else);
|
|
stmt->ctx_else->is_last = last;
|
|
|
|
emit_stmt(stmt->ctx_else, stmt->stmt_else, last);
|
|
|
|
last ? emit_opcode(ctx, opcode::OP_End) : emit_remove_local_vars(stmt->ctx_else);
|
|
|
|
if(stmt->ctx_else->abort == abort_t::abort_none)
|
|
childs.push_back(stmt->ctx_else.get());
|
|
|
|
insert_label(end_loc);
|
|
|
|
ctx->init_from_child(childs);
|
|
}
|
|
|
|
void compiler::emit_stmt_while(const gsc::context_ptr& ctx, const gsc::stmt_while_ptr& stmt)
|
|
{
|
|
auto old_breaks = break_ctxs_;
|
|
auto old_continues = continue_ctxs_;
|
|
auto old_break = can_break_;
|
|
auto old_continue = can_continue_;
|
|
break_ctxs_.clear();
|
|
continue_ctxs_.clear();
|
|
can_break_ = true;
|
|
can_continue_ = true;
|
|
|
|
auto break_loc = create_label();
|
|
auto continue_loc = create_label();
|
|
|
|
ctx->transfer(stmt->ctx);
|
|
stmt->ctx->loc_break = break_loc;
|
|
stmt->ctx->loc_continue = continue_loc;
|
|
|
|
emit_create_local_vars(stmt->ctx);
|
|
|
|
ctx->local_vars_create_count = stmt->ctx->local_vars_create_count;
|
|
|
|
for(auto i = 0; i < ctx->local_vars_create_count; i++)
|
|
{
|
|
if(!ctx->local_vars.at(i).init)
|
|
ctx->local_vars.at(i).init = true;
|
|
}
|
|
|
|
auto begin_loc = insert_label();
|
|
|
|
bool const_cond = is_constant_condition(stmt->expr);
|
|
|
|
if(!const_cond)
|
|
{
|
|
emit_expr(ctx, stmt->expr);
|
|
emit_opcode(ctx, opcode::OP_JumpOnFalse, break_loc);
|
|
}
|
|
|
|
emit_stmt(stmt->ctx, stmt->stmt, false);
|
|
|
|
insert_label(continue_loc);
|
|
emit_opcode(ctx, opcode::OP_jumpback, begin_loc);
|
|
|
|
insert_label(break_loc);
|
|
|
|
if(const_cond)
|
|
ctx->init_from_child(break_ctxs_);
|
|
|
|
can_break_ = old_break;
|
|
can_continue_ = old_continue;
|
|
break_ctxs_ = old_breaks;
|
|
continue_ctxs_ = old_continues;
|
|
}
|
|
|
|
void compiler::emit_stmt_for(const gsc::context_ptr& ctx, const gsc::stmt_for_ptr& stmt)
|
|
{
|
|
auto old_breaks = break_ctxs_;
|
|
auto old_continues = continue_ctxs_;
|
|
auto old_break = can_break_;
|
|
auto old_continue = can_continue_;
|
|
break_ctxs_.clear();
|
|
continue_ctxs_.clear();
|
|
can_break_ = false;
|
|
can_continue_ = false;
|
|
|
|
auto break_loc = create_label();
|
|
auto continue_loc = create_label();
|
|
|
|
emit_stmt(ctx, stmt->pre_expr, false);
|
|
|
|
ctx->transfer(stmt->ctx);
|
|
stmt->ctx->loc_break = break_loc;
|
|
stmt->ctx->loc_continue = continue_loc;
|
|
|
|
emit_create_local_vars(stmt->ctx);
|
|
|
|
ctx->local_vars_create_count = stmt->ctx->local_vars_create_count;
|
|
|
|
for(auto i = 0; i < ctx->local_vars_create_count; i++)
|
|
{
|
|
if(!ctx->local_vars.at(i).init)
|
|
ctx->local_vars.at(i).init = true;
|
|
}
|
|
|
|
ctx->transfer(stmt->ctx_post);
|
|
|
|
auto begin_loc = insert_label();
|
|
|
|
bool const_cond = is_constant_condition(stmt->expr);
|
|
|
|
if(!const_cond)
|
|
{
|
|
emit_expr(ctx, stmt->expr);
|
|
emit_opcode(ctx, opcode::OP_JumpOnFalse, break_loc);
|
|
}
|
|
|
|
can_break_ = true;
|
|
can_continue_ = true;
|
|
|
|
emit_stmt(stmt->ctx, stmt->stmt, false);
|
|
|
|
if(stmt->ctx->abort == abort_t::abort_none)
|
|
continue_ctxs_.push_back(stmt->ctx.get());
|
|
|
|
can_break_ = false;
|
|
can_continue_ = false;
|
|
|
|
insert_label(continue_loc);
|
|
|
|
stmt->ctx_post->init_from_child(continue_ctxs_);
|
|
|
|
emit_stmt(stmt->ctx_post, stmt->post_expr, false);
|
|
emit_opcode(ctx, opcode::OP_jumpback, begin_loc);
|
|
|
|
insert_label(break_loc);
|
|
|
|
if(const_cond)
|
|
ctx->init_from_child(break_ctxs_);
|
|
|
|
can_break_ = old_break;
|
|
can_continue_ = old_continue;
|
|
break_ctxs_ = old_breaks;
|
|
continue_ctxs_ = old_continues;
|
|
}
|
|
|
|
void compiler::emit_stmt_foreach(const gsc::context_ptr& ctx, const gsc::stmt_foreach_ptr& stmt)
|
|
{
|
|
auto old_breaks = break_ctxs_;
|
|
auto old_continues = continue_ctxs_;
|
|
auto old_break = can_break_;
|
|
auto old_continue = can_continue_;
|
|
break_ctxs_.clear();
|
|
continue_ctxs_.clear();
|
|
can_break_ = false;
|
|
can_continue_ = false;
|
|
|
|
auto break_loc = create_label();
|
|
auto continue_loc = create_label();
|
|
|
|
emit_expr(ctx, stmt->array_expr);
|
|
emit_variable_ref(ctx, stmt->array, true);
|
|
emit_variable(ctx, stmt->array);
|
|
emit_opcode(ctx, opcode::OP_CallBuiltin1, "getfirstarraykey");
|
|
emit_variable_ref(ctx, stmt->key_expr, true);
|
|
|
|
ctx->transfer(stmt->ctx);
|
|
stmt->ctx->loc_break = break_loc;
|
|
stmt->ctx->loc_continue = continue_loc;
|
|
|
|
emit_create_local_vars(stmt->ctx);
|
|
|
|
ctx->local_vars_create_count = stmt->ctx->local_vars_create_count;
|
|
|
|
for(auto i = 0; i < ctx->local_vars_create_count; i++)
|
|
{
|
|
if(!ctx->local_vars.at(i).init)
|
|
ctx->local_vars.at(i).init = true;
|
|
}
|
|
|
|
ctx->transfer(stmt->ctx_post);
|
|
|
|
auto begin_loc = insert_label();
|
|
|
|
emit_variable(ctx, stmt->key_expr);
|
|
emit_opcode(ctx, opcode::OP_CallBuiltin1, "isdefined");
|
|
emit_opcode(ctx, opcode::OP_JumpOnFalse, break_loc);
|
|
|
|
can_break_ = true;
|
|
can_continue_ = true;
|
|
|
|
emit_variable(stmt->ctx, stmt->key_expr);
|
|
emit_opcode(stmt->ctx, opcode::OP_EvalLocalArrayCached, variable_access_index(stmt->ctx, stmt->array.as_name));
|
|
emit_variable_ref(stmt->ctx, stmt->value_expr, true);
|
|
emit_stmt(stmt->ctx, stmt->stmt, false);
|
|
|
|
if(stmt->ctx->abort == abort_t::abort_none)
|
|
continue_ctxs_.push_back(stmt->ctx.get());
|
|
|
|
can_break_ = false;
|
|
can_continue_ = false;
|
|
|
|
insert_label(continue_loc);
|
|
|
|
stmt->ctx_post->init_from_child(continue_ctxs_);
|
|
|
|
emit_variable(stmt->ctx_post, stmt->key_expr);
|
|
emit_variable(stmt->ctx_post, stmt->array);
|
|
emit_opcode(stmt->ctx_post, opcode::OP_CallBuiltin2, "getnextarraykey");
|
|
emit_variable_ref(stmt->ctx_post, stmt->key_expr, true);
|
|
emit_opcode(ctx, opcode::OP_jumpback, begin_loc);
|
|
|
|
insert_label(break_loc);
|
|
emit_clear_local_variable(ctx, stmt->array.as_name);
|
|
if(!stmt->use_key) emit_clear_local_variable(ctx, stmt->key_expr.as_name);
|
|
|
|
can_break_ = old_break;
|
|
can_continue_ = old_continue;
|
|
break_ctxs_ = old_breaks;
|
|
continue_ctxs_ = old_continues;
|
|
}
|
|
|
|
void compiler::emit_stmt_switch(const gsc::context_ptr& ctx, const gsc::stmt_switch_ptr& stmt)
|
|
{
|
|
auto old_breaks = break_ctxs_;
|
|
auto old_break = can_break_;
|
|
break_ctxs_.clear();
|
|
can_break_ = false;
|
|
|
|
auto jmptable_loc = create_label();
|
|
auto break_loc = create_label();
|
|
|
|
emit_expr(ctx, stmt->expr);
|
|
emit_opcode(ctx, opcode::OP_switch, jmptable_loc);
|
|
|
|
can_break_ = true;
|
|
|
|
std::vector<std::string> data;
|
|
data.push_back(utils::string::va("%d", stmt->stmt->stmts.size()));
|
|
|
|
bool has_default = false;
|
|
gsc::context* default_ctx = nullptr;
|
|
|
|
for(auto i = 0; i < stmt->stmt->stmts.size(); i++)
|
|
{
|
|
auto& entry = stmt->stmt->stmts[i];
|
|
|
|
if(entry.as_node->type == gsc::node_t::stmt_case)
|
|
{
|
|
if(has_default)
|
|
{
|
|
gsc::comp_error(stmt->loc, "default must be last case");
|
|
}
|
|
|
|
auto& case_ = entry.as_case;
|
|
if(case_->value.as_node->type == gsc::node_t::data_integer)
|
|
{
|
|
auto loc = insert_label();
|
|
data.push_back("case");
|
|
data.push_back(case_->value.as_integer->value);
|
|
data.push_back(loc);
|
|
}
|
|
else if(case_->value.as_node->type == gsc::node_t::data_string)
|
|
{
|
|
auto loc = insert_label();
|
|
data.push_back("case");
|
|
data.push_back(case_->value.as_string->value);
|
|
data.push_back(loc);
|
|
}
|
|
else
|
|
{
|
|
throw gsc::comp_error(stmt->loc, "case type must be int or string");
|
|
}
|
|
|
|
ctx->transfer(case_->ctx);
|
|
case_->ctx->loc_break = break_loc;
|
|
emit_stmt_list(case_->ctx, case_->stmt, false);
|
|
if(case_->stmt->stmts.size() > 0)
|
|
emit_remove_local_vars(case_->ctx);
|
|
}
|
|
else if(entry.as_node->type == gsc::node_t::stmt_default)
|
|
{
|
|
auto loc = insert_label();
|
|
data.push_back("default");
|
|
data.push_back(loc);
|
|
|
|
has_default = true;
|
|
default_ctx = entry.as_default->ctx.get();
|
|
|
|
ctx->transfer(entry.as_default->ctx);
|
|
entry.as_default->ctx->loc_break = break_loc;
|
|
emit_stmt_list(entry.as_default->ctx, entry.as_default->stmt, false);
|
|
if(entry.as_default->stmt->stmts.size() > 0)
|
|
emit_remove_local_vars(entry.as_default->ctx);
|
|
}
|
|
else
|
|
{
|
|
throw gsc::comp_error(entry.as_node->loc, "missing case statement");
|
|
}
|
|
}
|
|
|
|
if(has_default)
|
|
{
|
|
if(default_ctx->abort == abort_t::abort_none)
|
|
{
|
|
break_ctxs_.push_back(default_ctx);
|
|
}
|
|
ctx->init_from_child(break_ctxs_);
|
|
}
|
|
|
|
insert_label(jmptable_loc);
|
|
|
|
emit_opcode(ctx, opcode::OP_endswitch, data);
|
|
|
|
auto offset = 7 * stmt->stmt->stmts.size();
|
|
function_->instructions.back()->size += offset;
|
|
index_ += offset;
|
|
|
|
insert_label(break_loc);
|
|
|
|
can_break_ = old_break;
|
|
break_ctxs_ = old_breaks;
|
|
}
|
|
|
|
void compiler::emit_stmt_case(const gsc::context_ptr& ctx, const gsc::stmt_case_ptr& stmt)
|
|
{
|
|
throw gsc::comp_error(stmt->loc, "illegal case statement");
|
|
}
|
|
|
|
void compiler::emit_stmt_default(const gsc::context_ptr& ctx, const gsc::stmt_default_ptr& stmt)
|
|
{
|
|
throw gsc::comp_error(stmt->loc, "illegal default statement");
|
|
}
|
|
|
|
void compiler::emit_stmt_break(const gsc::context_ptr& ctx, const gsc::stmt_break_ptr& stmt)
|
|
{
|
|
if(can_break_ && ctx->abort == abort_t::abort_none && ctx->loc_break != "")
|
|
{
|
|
break_ctxs_.push_back(ctx.get());
|
|
emit_remove_local_vars(ctx);
|
|
ctx->abort = abort_t::abort_break;
|
|
emit_opcode(ctx, opcode::OP_jump, ctx->loc_break);
|
|
}
|
|
else
|
|
{
|
|
throw gsc::comp_error(stmt->loc, "illegal break statement");
|
|
}
|
|
}
|
|
|
|
void compiler::emit_stmt_continue(const gsc::context_ptr& ctx, const gsc::stmt_continue_ptr& stmt)
|
|
{
|
|
if(can_break_ && ctx->abort == abort_t::abort_none && ctx->loc_continue != "")
|
|
{
|
|
continue_ctxs_.push_back(ctx.get());
|
|
emit_remove_local_vars(ctx);
|
|
ctx->abort = abort_t::abort_continue;
|
|
emit_opcode(ctx, opcode::OP_jump, ctx->loc_continue);
|
|
}
|
|
else
|
|
{
|
|
throw gsc::comp_error(stmt->loc, "illegal continue statement");
|
|
}
|
|
}
|
|
|
|
void compiler::emit_stmt_return(const gsc::context_ptr& ctx, const gsc::stmt_return_ptr& stmt)
|
|
{
|
|
if(ctx->abort == abort_t::abort_none)
|
|
{
|
|
ctx->abort = abort_t::abort_return;
|
|
}
|
|
|
|
if(stmt->expr.as_node->type == gsc::node_t::null)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_End);
|
|
}
|
|
else
|
|
{
|
|
emit_expr(ctx, stmt->expr);
|
|
emit_opcode(ctx, opcode::OP_Return);
|
|
}
|
|
}
|
|
|
|
void compiler::emit_expr(const gsc::context_ptr& ctx, const gsc::expr_ptr& expr)
|
|
{
|
|
switch(expr.as_node->type)
|
|
{
|
|
case gsc::node_t::expr_and: emit_expr_and(ctx, expr.as_and); break;
|
|
case gsc::node_t::expr_or: emit_expr_or(ctx, expr.as_or); break;
|
|
case gsc::node_t::expr_equality: emit_expr_binary(ctx, expr.as_binary); break;
|
|
case gsc::node_t::expr_inequality: emit_expr_binary(ctx, expr.as_binary); break;
|
|
case gsc::node_t::expr_less: emit_expr_binary(ctx, expr.as_binary); break;
|
|
case gsc::node_t::expr_greater: emit_expr_binary(ctx, expr.as_binary); break;
|
|
case gsc::node_t::expr_less_equal: emit_expr_binary(ctx, expr.as_binary); break;
|
|
case gsc::node_t::expr_greater_equal: emit_expr_binary(ctx, expr.as_binary); break;
|
|
case gsc::node_t::expr_bitwise_or: emit_expr_binary(ctx, expr.as_binary); break;
|
|
case gsc::node_t::expr_bitwise_and: emit_expr_binary(ctx, expr.as_binary); break;
|
|
case gsc::node_t::expr_bitwise_exor: emit_expr_binary(ctx, expr.as_binary); break;
|
|
case gsc::node_t::expr_shift_left: emit_expr_binary(ctx, expr.as_binary); break;
|
|
case gsc::node_t::expr_shift_right: emit_expr_binary(ctx, expr.as_binary); break;
|
|
case gsc::node_t::expr_add: emit_expr_binary(ctx, expr.as_binary); break;
|
|
case gsc::node_t::expr_sub: emit_expr_binary(ctx, expr.as_binary); break;
|
|
case gsc::node_t::expr_mult: emit_expr_binary(ctx, expr.as_binary); break;
|
|
case gsc::node_t::expr_div: emit_expr_binary(ctx, expr.as_binary); break;
|
|
case gsc::node_t::expr_mod: emit_expr_binary(ctx, expr.as_binary); break;
|
|
case gsc::node_t::expr_complement: emit_expr_complement(ctx, expr.as_complement); break;
|
|
case gsc::node_t::expr_not: emit_expr_not(ctx, expr.as_not); break;
|
|
case gsc::node_t::expr_call: emit_expr_call(ctx, expr.as_call); break;
|
|
case gsc::node_t::expr_function: emit_expr_function(ctx, expr.as_function); break;
|
|
case gsc::node_t::expr_add_array: emit_expr_add_array(ctx, expr.as_add_array); break;
|
|
case gsc::node_t::expr_array: emit_array_variable(ctx, expr.as_array); break;
|
|
case gsc::node_t::expr_field: emit_field_variable(ctx, expr.as_field); break;
|
|
case gsc::node_t::expr_size: emit_expr_size(ctx, expr.as_size); break;
|
|
case gsc::node_t::data_thisthread: emit_opcode(ctx, opcode::OP_GetThisthread); break;
|
|
case gsc::node_t::data_empty_array: emit_opcode(ctx, opcode::OP_EmptyArray); break;
|
|
case gsc::node_t::data_undefined: emit_opcode(ctx, opcode::OP_GetUndefined); break;
|
|
case gsc::node_t::data_game: emit_opcode(ctx, opcode::OP_GetGame); break;
|
|
case gsc::node_t::data_self: emit_opcode(ctx, opcode::OP_GetSelf); break;
|
|
case gsc::node_t::data_anim: emit_opcode(ctx, opcode::OP_GetAnim); break;
|
|
case gsc::node_t::data_level: emit_opcode(ctx, opcode::OP_GetLevel); break;
|
|
case gsc::node_t::data_animation: emit_animation(ctx, expr.as_animation); break;
|
|
case gsc::node_t::data_animtree: emit_animtree(ctx, expr.as_animtree); break;
|
|
case gsc::node_t::data_name: emit_local_variable(ctx, expr.as_name); break;
|
|
case gsc::node_t::data_istring: emit_istring(ctx, expr.as_istring); break;
|
|
case gsc::node_t::data_string: emit_string(ctx, expr.as_string); break;
|
|
case gsc::node_t::data_vector: emit_vector(ctx, expr.as_vector); break;
|
|
case gsc::node_t::data_float: emit_float(ctx, expr.as_float); break;
|
|
case gsc::node_t::data_integer: emit_integer(ctx, expr.as_integer); break;
|
|
case gsc::node_t::data_false: emit_false(ctx, expr.as_false); break;
|
|
case gsc::node_t::data_true: emit_true(ctx, expr.as_true); break;
|
|
default: throw gsc::comp_error(expr.as_node->loc, "unknown expression"); break;
|
|
}
|
|
}
|
|
|
|
void compiler::emit_expr_assign(const gsc::context_ptr& ctx, const gsc::expr_assign_ptr& expr)
|
|
{
|
|
if(expr->type == gsc::node_t::expr_increment)
|
|
{
|
|
emit_variable_ref(ctx, expr->lvalue, false);
|
|
emit_opcode(ctx, opcode::OP_inc);
|
|
emit_opcode(ctx, opcode::OP_SetVariableField);
|
|
}
|
|
else if(expr->type == gsc::node_t::expr_decrement)
|
|
{
|
|
emit_variable_ref(ctx, expr->lvalue, false);
|
|
emit_opcode(ctx, opcode::OP_dec);
|
|
emit_opcode(ctx, opcode::OP_SetVariableField);
|
|
}
|
|
else if(expr->type == gsc::node_t::expr_assign_equal)
|
|
{
|
|
if(expr->rvalue.as_node->type == gsc::node_t::data_undefined)
|
|
{
|
|
emit_expr_clear_variable(ctx, expr->lvalue);
|
|
}
|
|
else
|
|
{
|
|
emit_expr(ctx, expr->rvalue);
|
|
emit_variable_ref(ctx, expr->lvalue, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
emit_expr(ctx, expr->lvalue);
|
|
emit_expr(ctx, expr->rvalue);
|
|
|
|
switch(expr->type)
|
|
{
|
|
case gsc::node_t::expr_assign_add: emit_opcode(ctx, opcode::OP_plus); break;
|
|
case gsc::node_t::expr_assign_sub: emit_opcode(ctx, opcode::OP_minus); break;
|
|
case gsc::node_t::expr_assign_mult: emit_opcode(ctx, opcode::OP_multiply); break;
|
|
case gsc::node_t::expr_assign_div: emit_opcode(ctx, opcode::OP_divide); break;
|
|
case gsc::node_t::expr_assign_mod: emit_opcode(ctx, opcode::OP_mod); break;
|
|
case gsc::node_t::expr_assign_shift_left: emit_opcode(ctx, opcode::OP_shift_left); break;
|
|
case gsc::node_t::expr_assign_shift_right: emit_opcode(ctx, opcode::OP_shift_right); break;
|
|
case gsc::node_t::expr_assign_bitwise_or: emit_opcode(ctx, opcode::OP_bit_or); break;
|
|
case gsc::node_t::expr_assign_bitwise_and: emit_opcode(ctx, opcode::OP_bit_and); break;
|
|
case gsc::node_t::expr_assign_bitwise_exor: emit_opcode(ctx, opcode::OP_bit_ex_or); break;
|
|
default: throw gsc::comp_error(expr->loc, "unknown assign operation"); break;
|
|
}
|
|
|
|
emit_variable_ref(ctx, expr->lvalue, true);
|
|
}
|
|
}
|
|
|
|
void compiler::emit_expr_binary(const gsc::context_ptr& ctx, const gsc::expr_binary_ptr& expr)
|
|
{
|
|
emit_expr(ctx, expr->lvalue);
|
|
emit_expr(ctx, expr->rvalue);
|
|
|
|
switch(expr->type)
|
|
{
|
|
case gsc::node_t::expr_equality: emit_opcode(ctx, opcode::OP_equality); break;
|
|
case gsc::node_t::expr_inequality: emit_opcode(ctx, opcode::OP_inequality); break;
|
|
case gsc::node_t::expr_less: emit_opcode(ctx, opcode::OP_less); break;
|
|
case gsc::node_t::expr_greater: emit_opcode(ctx, opcode::OP_greater); break;
|
|
case gsc::node_t::expr_less_equal: emit_opcode(ctx, opcode::OP_less_equal); break;
|
|
case gsc::node_t::expr_greater_equal: emit_opcode(ctx, opcode::OP_greater_equal); break;
|
|
case gsc::node_t::expr_bitwise_or: emit_opcode(ctx, opcode::OP_bit_or); break;
|
|
case gsc::node_t::expr_bitwise_and: emit_opcode(ctx, opcode::OP_bit_and); break;
|
|
case gsc::node_t::expr_bitwise_exor: emit_opcode(ctx, opcode::OP_bit_ex_or); break;
|
|
case gsc::node_t::expr_shift_left: emit_opcode(ctx, opcode::OP_shift_left); break;
|
|
case gsc::node_t::expr_shift_right: emit_opcode(ctx, opcode::OP_shift_right); break;
|
|
case gsc::node_t::expr_add: emit_opcode(ctx, opcode::OP_plus); break;
|
|
case gsc::node_t::expr_sub: emit_opcode(ctx, opcode::OP_minus); break;
|
|
case gsc::node_t::expr_mult: emit_opcode(ctx, opcode::OP_multiply); break;
|
|
case gsc::node_t::expr_div: emit_opcode(ctx, opcode::OP_divide); break;
|
|
case gsc::node_t::expr_mod: emit_opcode(ctx, opcode::OP_mod); break;
|
|
default: throw gsc::comp_error(expr->loc, "unknown binary expression"); break;
|
|
}
|
|
}
|
|
|
|
void compiler::emit_expr_and(const gsc::context_ptr& ctx, const gsc::expr_and_ptr& expr)
|
|
{
|
|
auto label = create_label();
|
|
|
|
emit_expr(ctx, expr->lvalue);
|
|
emit_opcode(ctx, opcode::OP_JumpOnFalseExpr, label);
|
|
emit_expr(ctx, expr->rvalue);
|
|
emit_opcode(ctx, opcode::OP_CastBool);
|
|
|
|
insert_label(label);
|
|
}
|
|
|
|
void compiler::emit_expr_or(const gsc::context_ptr& ctx, const gsc::expr_or_ptr& expr)
|
|
{
|
|
auto label = create_label();
|
|
|
|
emit_expr(ctx, expr->lvalue);
|
|
emit_opcode(ctx, opcode::OP_JumpOnTrueExpr, label);
|
|
emit_expr(ctx, expr->rvalue);
|
|
emit_opcode(ctx, opcode::OP_CastBool);
|
|
|
|
insert_label(label);
|
|
}
|
|
|
|
void compiler::emit_expr_complement(const gsc::context_ptr& ctx, const gsc::expr_complement_ptr& expr)
|
|
{
|
|
emit_expr(ctx, expr->rvalue);
|
|
emit_opcode(ctx, opcode::OP_BoolComplement);
|
|
}
|
|
|
|
void compiler::emit_expr_not(const gsc::context_ptr& ctx, const gsc::expr_not_ptr& expr)
|
|
{
|
|
emit_expr(ctx, expr->rvalue);
|
|
emit_opcode(ctx, opcode::OP_BoolNot);
|
|
}
|
|
|
|
void compiler::emit_expr_call(const gsc::context_ptr& ctx, const gsc::expr_call_ptr& expr)
|
|
{
|
|
if(expr->func.as_node->type == gsc::node_t::expr_call_pointer)
|
|
{
|
|
emit_expr_call_pointer(ctx, expr);
|
|
}
|
|
else
|
|
{
|
|
emit_expr_call_function(ctx, expr);
|
|
}
|
|
}
|
|
|
|
void compiler::emit_expr_call_pointer(const gsc::context_ptr& ctx, const gsc::expr_call_ptr& expr)
|
|
{
|
|
bool thread = expr->thread;
|
|
bool child = expr->child;
|
|
bool method = expr->obj.as_node->type != gsc::node_t::null ? true : false;
|
|
bool builtin = builtin = expr->func.as_pointer->builtin;
|
|
std::uint32_t args = expr->func.as_pointer->args->list.size();
|
|
|
|
if(thread && child || thread && builtin || child && builtin)
|
|
throw gsc::comp_error(expr->loc, "function call have more than 1 type (thread, childthread, builtin)");
|
|
|
|
if(!thread && !child && !builtin) emit_opcode(ctx, opcode::OP_PreScriptCall);
|
|
|
|
emit_expr_arguments(ctx, expr->func.as_pointer->args);
|
|
|
|
if(method) emit_expr(ctx, expr->obj);
|
|
|
|
emit_expr(ctx, expr->func.as_pointer->expr);
|
|
emit_expr_call_pointer_type(ctx, args, builtin, method, thread, child);
|
|
}
|
|
|
|
void compiler::emit_expr_call_pointer_type(const gsc::context_ptr& ctx, int args, bool builtin, bool method, bool thread, bool child)
|
|
{
|
|
if(builtin && !method)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_CallBuiltinPointer, utils::string::va("%d", args));
|
|
}
|
|
else if(builtin && method)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_CallBuiltinMethodPointer, utils::string::va("%d", args));
|
|
}
|
|
else if(thread && !method && !child)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ScriptThreadCallPointer, utils::string::va("%d", args));
|
|
}
|
|
else if(thread && method && !child)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ScriptMethodThreadCallPointer, utils::string::va("%d", args));
|
|
}
|
|
else if (child && !method && !thread)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ScriptChildThreadCallPointer, utils::string::va("%d", args));
|
|
}
|
|
else if(child && method && !thread)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ScriptMethodChildThreadCallPointer, utils::string::va("%d", args));
|
|
}
|
|
else
|
|
{
|
|
method ? emit_opcode(ctx, opcode::OP_ScriptMethodCallPointer) : emit_opcode(ctx, opcode::OP_ScriptFunctionCallPointer);
|
|
}
|
|
}
|
|
|
|
void compiler::emit_expr_call_function(const gsc::context_ptr& ctx, const gsc::expr_call_ptr& expr)
|
|
{
|
|
bool thread = expr->thread;
|
|
bool child = expr->child;
|
|
bool method = expr->obj.as_node->type != gsc::node_t::null ? true : false;
|
|
std::uint32_t args = expr->func.as_func->args->list.size();
|
|
auto name = expr->func.as_func->name->value;
|
|
auto file = expr->func.as_func->file->value;
|
|
|
|
bool builtin = false, far = false, local = false;
|
|
|
|
if(file != "") far = true;
|
|
else
|
|
{
|
|
if(is_local_call(name)) local = true;
|
|
else if(method && is_builtin_method(name)) builtin = true;
|
|
else if(!method && is_builtin_func(name)) builtin = true;
|
|
else
|
|
{
|
|
for(const auto& inc : includes_)
|
|
{
|
|
for(const auto& fun : inc.funcs)
|
|
{
|
|
if(name == fun)
|
|
{
|
|
far = true;
|
|
file = inc.name;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!builtin && !far && !local)
|
|
throw gsc::comp_error(expr->loc, "unknown function call " + name);
|
|
}
|
|
}
|
|
|
|
if(thread && child || thread && builtin || child && builtin)
|
|
throw gsc::comp_error(expr->loc, "function call have more than 1 type (thread, childthread, builtin)");
|
|
|
|
if(!thread && !child && !builtin && !(!method && args == 0))
|
|
emit_opcode(ctx, opcode::OP_PreScriptCall);
|
|
|
|
emit_expr_arguments(ctx, expr->func.as_func->args);
|
|
|
|
if(method) emit_expr(ctx, expr->obj);
|
|
|
|
if(builtin) emit_expr_call_function_builtin(ctx, name, args, method);
|
|
else if(local) emit_expr_call_function_local(ctx, name, args, method, thread, child);
|
|
else if(far) emit_expr_call_function_far(ctx, file, name, args, method, thread, child);
|
|
}
|
|
|
|
void compiler::emit_expr_call_function_builtin(const gsc::context_ptr& ctx, const std::string& func, int args, bool method)
|
|
{
|
|
if(method)
|
|
{
|
|
switch(args)
|
|
{
|
|
case 0: emit_opcode(ctx, opcode::OP_CallBuiltinMethod0, func); break;
|
|
case 1: emit_opcode(ctx, opcode::OP_CallBuiltinMethod1, func); break;
|
|
case 2: emit_opcode(ctx, opcode::OP_CallBuiltinMethod2, func); break;
|
|
case 3: emit_opcode(ctx, opcode::OP_CallBuiltinMethod3, func); break;
|
|
case 4: emit_opcode(ctx, opcode::OP_CallBuiltinMethod4, func); break;
|
|
case 5: emit_opcode(ctx, opcode::OP_CallBuiltinMethod5, func); break;
|
|
default: emit_opcode(ctx, opcode::OP_CallBuiltinMethod, { utils::string::va("%d", args), func }); break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch(args)
|
|
{
|
|
case 0: emit_opcode(ctx, opcode::OP_CallBuiltin0, func); break;
|
|
case 1: emit_opcode(ctx, opcode::OP_CallBuiltin1, func); break;
|
|
case 2: emit_opcode(ctx, opcode::OP_CallBuiltin2, func); break;
|
|
case 3: emit_opcode(ctx, opcode::OP_CallBuiltin3, func); break;
|
|
case 4: emit_opcode(ctx, opcode::OP_CallBuiltin4, func); break;
|
|
case 5: emit_opcode(ctx, opcode::OP_CallBuiltin5, func); break;
|
|
default: emit_opcode(ctx, opcode::OP_CallBuiltin, { utils::string::va("%d", args), func }); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void compiler::emit_expr_call_function_local(const gsc::context_ptr& ctx, const std::string& func, int args, bool method, bool thread, bool child)
|
|
{
|
|
if(thread && !method && !child)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ScriptLocalThreadCall, { func, utils::string::va("%d", args) });
|
|
}
|
|
else if(thread && method && !child)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ScriptLocalMethodThreadCall, { func, utils::string::va("%d", args) });
|
|
}
|
|
else if(child && !method && !thread)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ScriptLocalChildThreadCall, { func, utils::string::va("%d", args) });
|
|
}
|
|
else if(child && method && !thread)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ScriptLocalMethodChildThreadCall, { func, utils::string::va("%d", args) });
|
|
}
|
|
else if(method && !thread && !child)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ScriptLocalMethodCall, func);
|
|
}
|
|
else if(!thread && !child && !method && args == 0)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ScriptLocalFunctionCall2, func);
|
|
}
|
|
else if(!thread && !child && !method && args != 0)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ScriptLocalFunctionCall, func);
|
|
}
|
|
}
|
|
|
|
void compiler::emit_expr_call_function_far(const gsc::context_ptr& ctx, const std::string& file, const std::string& func, int args, bool method, bool thread, bool child)
|
|
{
|
|
if(thread && !method && !child)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ScriptFarThreadCall, { utils::string::va("%d", args), file, func });
|
|
}
|
|
else if(thread && method && !child)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ScriptFarMethodThreadCall, { utils::string::va("%d", args), file, func });
|
|
}
|
|
else if(child && !method && !thread)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ScriptFarChildThreadCall, { utils::string::va("%d", args), file, func });
|
|
}
|
|
else if(child && method && !thread)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ScriptFarMethodChildThreadCall, { utils::string::va("%d", args), file, func });
|
|
}
|
|
else if(!thread && !child && method)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ScriptFarMethodCall, { file, func });
|
|
}
|
|
else if(!thread && !child && !method && args == 0)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ScriptFarFunctionCall2, { file, func });
|
|
}
|
|
else if(!thread && !child && !method && args != 0)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ScriptFarFunctionCall, { file, func });
|
|
}
|
|
}
|
|
|
|
void compiler::emit_expr_arguments(const gsc::context_ptr& ctx, const gsc::expr_arguments_ptr& args)
|
|
{
|
|
std::reverse(args->list.begin(), args->list.end());
|
|
|
|
for(auto& arg : args->list)
|
|
{
|
|
emit_expr(ctx, arg);
|
|
}
|
|
}
|
|
|
|
void compiler::emit_expr_function(const gsc::context_ptr& ctx, const gsc::expr_function_ptr& expr)
|
|
{
|
|
bool far = false, local = false, builtin = false, method = false;
|
|
auto name = expr->name->value;
|
|
auto file = expr->file->value;
|
|
|
|
if(file != "")
|
|
{
|
|
far = true;
|
|
}
|
|
else if(is_include_call(name, file))
|
|
{
|
|
far = true;
|
|
}
|
|
else if(is_builtin_method(name))
|
|
{
|
|
builtin = true;
|
|
method = true;
|
|
}
|
|
else if(is_builtin_func(name))
|
|
{
|
|
builtin = true;
|
|
}
|
|
else if(is_local_call(name))
|
|
{
|
|
local = true;
|
|
}
|
|
else
|
|
{
|
|
throw gsc::comp_error(expr->loc, "couldn't determine function reference type");
|
|
}
|
|
|
|
if(local)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetLocalFunction, name);
|
|
}
|
|
else if(far)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetFarFunction, { file, name } );
|
|
}
|
|
else if(builtin && method)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetBuiltinMethod, name);
|
|
}
|
|
else if(builtin && !method)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetBuiltinFunction, name);
|
|
}
|
|
}
|
|
|
|
void compiler::emit_expr_clear_variable(const gsc::context_ptr& ctx, const gsc::expr_ptr& lvalue)
|
|
{
|
|
switch(lvalue.as_node->type)
|
|
{
|
|
case gsc::node_t::expr_array:
|
|
emit_expr(ctx, lvalue.as_array->key);
|
|
lvalue.as_array->obj.as_node->type == gsc::node_t::data_game ? emit_opcode(ctx, opcode::OP_GetGameRef) : emit_variable_ref(ctx, lvalue.as_array->obj, false);
|
|
emit_opcode(ctx, opcode::OP_ClearArray);
|
|
break;
|
|
case gsc::node_t::expr_field:
|
|
emit_object(ctx, lvalue.as_field->obj);
|
|
emit_opcode(ctx, opcode::OP_ClearFieldVariable,lvalue.as_field->field->value);
|
|
break;
|
|
case gsc::node_t::data_name:
|
|
emit_opcode(ctx, opcode::OP_GetUndefined);
|
|
emit_local_variable_ref(ctx, lvalue.as_name, true);
|
|
break;
|
|
default:
|
|
throw gsc::comp_error(lvalue.as_node->loc, "unknown clear variable lvalue");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void compiler::emit_expr_add_array(const gsc::context_ptr& ctx, const gsc::expr_add_array_ptr& expr)
|
|
{
|
|
if(expr->args->list.size() <= 0)
|
|
{
|
|
throw gsc::comp_error(expr->loc, "invalid empty add array. did u mean '[]' ?");
|
|
}
|
|
|
|
emit_opcode(ctx, opcode::OP_EmptyArray);
|
|
|
|
for(const auto& arg : expr->args->list)
|
|
{
|
|
emit_expr(ctx, arg);
|
|
emit_opcode(ctx, opcode::OP_AddArray);
|
|
}
|
|
}
|
|
|
|
void compiler::emit_expr_size(const gsc::context_ptr& ctx, const gsc::expr_size_ptr& expr)
|
|
{
|
|
emit_variable(ctx, expr->obj);
|
|
emit_opcode(ctx, opcode::OP_size);
|
|
}
|
|
|
|
void compiler::emit_variable_ref(const gsc::context_ptr& ctx, const gsc::expr_ptr& expr, bool set)
|
|
{
|
|
switch(expr.as_node->type)
|
|
{
|
|
case gsc::node_t::expr_array: emit_array_variable_ref(ctx, expr.as_array, set); break;
|
|
case gsc::node_t::expr_field: emit_field_variable_ref(ctx, expr.as_field, set); break;
|
|
case gsc::node_t::data_name: emit_local_variable_ref(ctx, expr.as_name, set); break;
|
|
default: throw gsc::comp_error(expr.as_node->loc, "invalid variable reference type."); break;
|
|
}
|
|
}
|
|
|
|
void compiler::emit_array_variable_ref(const gsc::context_ptr& ctx, const gsc::expr_array_ptr& expr, bool set)
|
|
{
|
|
emit_expr(ctx, expr->key);
|
|
|
|
switch(expr->obj.as_node->type)
|
|
{
|
|
case gsc::node_t::data_game:
|
|
emit_opcode(ctx, opcode::OP_GetGameRef);
|
|
emit_opcode(ctx, opcode::OP_EvalArrayRef);
|
|
if(set) emit_opcode(ctx, opcode::OP_SetVariableField);
|
|
break;
|
|
case gsc::node_t::expr_array:
|
|
case gsc::node_t::expr_field:
|
|
emit_variable_ref(ctx, expr->obj, false);
|
|
emit_opcode(ctx, opcode::OP_EvalArrayRef);
|
|
if(set) emit_opcode(ctx, opcode::OP_SetVariableField);
|
|
break;
|
|
case gsc::node_t::data_name:
|
|
{
|
|
if(!variable_initialized(ctx, expr->obj.as_name))
|
|
{
|
|
initialize_variable(ctx, expr->obj.as_name);
|
|
emit_opcode(ctx, opcode::OP_EvalNewLocalArrayRefCached0, variable_create_index(ctx, expr->obj.as_name));
|
|
|
|
if(!set)
|
|
{
|
|
throw gsc::comp_error(expr->loc, "INTERNAL: VAR CREATED BUT NOT SET!");
|
|
}
|
|
}
|
|
else if(variable_stack_index(ctx, expr->obj.as_name) == 0)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_EvalLocalArrayRefCached0);
|
|
}
|
|
else
|
|
{
|
|
emit_opcode(ctx, opcode::OP_EvalLocalArrayRefCached, variable_access_index(ctx, expr->obj.as_name));
|
|
}
|
|
|
|
if(set) emit_opcode(ctx, opcode::OP_SetVariableField);
|
|
}
|
|
break;
|
|
case gsc::node_t::expr_call:
|
|
throw gsc::comp_error(expr->loc, "call result can't be referenced.");
|
|
break;
|
|
default:
|
|
throw gsc::comp_error(expr->loc, "unknown array object type");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void compiler::emit_field_variable_ref(const gsc::context_ptr& ctx, const gsc::expr_field_ptr& expr, bool set)
|
|
{
|
|
const auto& field = expr->field->value;
|
|
|
|
switch(expr->obj.as_node->type)
|
|
{
|
|
case gsc::node_t::data_level:
|
|
set ? emit_opcode(ctx, opcode::OP_SetLevelFieldVariableField, field) : emit_opcode(ctx, opcode::OP_EvalLevelFieldVariableRef, field);
|
|
break;
|
|
case gsc::node_t::data_anim:
|
|
set ? emit_opcode(ctx, opcode::OP_SetAnimFieldVariableField, field) : emit_opcode(ctx, opcode::OP_EvalAnimFieldVariableRef, field);
|
|
break;
|
|
case gsc::node_t::data_self:
|
|
set ? emit_opcode(ctx, opcode::OP_SetSelfFieldVariableField, field) : emit_opcode(ctx, opcode::OP_EvalSelfFieldVariableRef, field);
|
|
break;
|
|
case gsc::node_t::expr_array:
|
|
emit_array_variable(ctx, expr->obj.as_array);
|
|
emit_opcode(ctx, opcode::OP_CastFieldObject);
|
|
emit_opcode(ctx, opcode::OP_EvalFieldVariableRef, field);
|
|
if(set) emit_opcode(ctx, opcode::OP_SetVariableField);
|
|
break;
|
|
case gsc::node_t::expr_field:
|
|
emit_field_variable(ctx, expr->obj.as_field);
|
|
emit_opcode(ctx, opcode::OP_CastFieldObject);
|
|
emit_opcode(ctx, opcode::OP_EvalFieldVariableRef, field);
|
|
if(set) emit_opcode(ctx, opcode::OP_SetVariableField);
|
|
break;
|
|
case gsc::node_t::data_name:
|
|
emit_opcode(ctx, opcode::OP_EvalLocalVariableObjectCached, variable_access_index(ctx, expr->obj.as_name));
|
|
emit_opcode(ctx, opcode::OP_EvalFieldVariableRef, field);
|
|
if(set) emit_opcode(ctx, opcode::OP_SetVariableField);
|
|
break;
|
|
case gsc::node_t::expr_call:
|
|
throw gsc::comp_error(expr->loc, "function call result can't be referenced");
|
|
break;
|
|
default:
|
|
throw gsc::comp_error(expr->loc, "unknown field variable object type");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void compiler::emit_local_variable_ref(const gsc::context_ptr& ctx, const gsc::name_ptr& expr, bool set)
|
|
{
|
|
const auto itr = constants_.find(expr->value);
|
|
|
|
if (itr != constants_.end())
|
|
{
|
|
throw gsc::comp_error(expr->loc, "variable name already defined as constant " + expr->value);
|
|
}
|
|
|
|
if(set)
|
|
{
|
|
if(!variable_initialized(ctx, expr))
|
|
{
|
|
initialize_variable(ctx, expr);
|
|
emit_opcode(ctx, opcode::OP_SetNewLocalVariableFieldCached0, variable_create_index(ctx, expr));
|
|
}
|
|
else if(variable_stack_index(ctx, expr) == 0)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_SetLocalVariableFieldCached0);
|
|
}
|
|
else
|
|
{
|
|
emit_opcode(ctx, opcode::OP_SetLocalVariableFieldCached, variable_access_index(ctx, expr));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto index = variable_stack_index(ctx, expr);
|
|
|
|
if(index == 0)
|
|
emit_opcode(ctx, opcode::OP_EvalLocalVariableRefCached0);
|
|
else
|
|
emit_opcode(ctx, opcode::OP_EvalLocalVariableRefCached, variable_access_index(ctx, expr));
|
|
}
|
|
}
|
|
|
|
void compiler::emit_variable(const gsc::context_ptr& ctx, const gsc::expr_ptr& expr)
|
|
{
|
|
// for obj.size
|
|
switch(expr.as_node->type)
|
|
{
|
|
case gsc::node_t::expr_array: emit_array_variable(ctx, expr.as_array); break;
|
|
case gsc::node_t::expr_field: emit_field_variable(ctx, expr.as_field); break;
|
|
case gsc::node_t::data_name: emit_local_variable(ctx, expr.as_name); break;
|
|
case gsc::node_t::expr_call: emit_expr_call(ctx, expr.as_call); break;
|
|
default: throw gsc::comp_error(expr.as_node->loc, "invalid variable type."); break;
|
|
}
|
|
}
|
|
|
|
void compiler::emit_array_variable(const gsc::context_ptr& ctx, const gsc::expr_array_ptr& expr)
|
|
{
|
|
emit_expr(ctx, expr->key);
|
|
|
|
if(expr->obj.as_node->type == gsc::node_t::data_name)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_EvalLocalArrayCached, variable_access_index(ctx, expr->obj.as_name));
|
|
}
|
|
else
|
|
{
|
|
emit_expr(ctx, expr->obj);
|
|
emit_opcode(ctx, opcode::OP_EvalArray);
|
|
}
|
|
}
|
|
|
|
void compiler::emit_field_variable(const gsc::context_ptr& ctx, const gsc::expr_field_ptr& expr)
|
|
{
|
|
const auto& field = expr->field->value;
|
|
|
|
switch(expr->obj.as_node->type)
|
|
{
|
|
case gsc::node_t::data_level:
|
|
emit_opcode(ctx, opcode::OP_EvalLevelFieldVariable, field);
|
|
break;
|
|
case gsc::node_t::data_anim:
|
|
emit_opcode(ctx, opcode::OP_EvalAnimFieldVariable, field);
|
|
break;
|
|
case gsc::node_t::data_self:
|
|
emit_opcode(ctx, opcode::OP_EvalSelfFieldVariable, field);
|
|
break;
|
|
case gsc::node_t::expr_array:
|
|
emit_array_variable(ctx, expr->obj.as_array);
|
|
emit_opcode(ctx, opcode::OP_CastFieldObject);
|
|
emit_opcode(ctx, opcode::OP_EvalFieldVariable, field);
|
|
break;
|
|
case gsc::node_t::expr_field:
|
|
emit_field_variable(ctx, expr->obj.as_field);
|
|
emit_opcode(ctx, opcode::OP_CastFieldObject);
|
|
emit_opcode(ctx, opcode::OP_EvalFieldVariable, field);
|
|
break;
|
|
case gsc::node_t::expr_call:
|
|
emit_expr_call(ctx, expr->obj.as_call);
|
|
emit_opcode(ctx, opcode::OP_CastFieldObject);
|
|
emit_opcode(ctx, opcode::OP_EvalFieldVariable, field);
|
|
break;
|
|
case gsc::node_t::data_name:
|
|
emit_opcode(ctx, opcode::OP_EvalLocalVariableObjectCached, variable_access_index(ctx, expr->obj.as_name));
|
|
emit_opcode(ctx, opcode::OP_EvalFieldVariable, field);
|
|
break;
|
|
default:
|
|
throw gsc::comp_error(expr->loc, "unknown field variable object type");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void compiler::emit_local_variable(const gsc::context_ptr& ctx, const gsc::name_ptr& expr)
|
|
{
|
|
// is constant ( should only allow: string, loc string, number, vector)
|
|
const auto itr = constants_.find(expr->value);
|
|
|
|
if (itr != constants_.end())
|
|
{
|
|
const auto& value = itr->second;
|
|
emit_expr(ctx, value);
|
|
return;
|
|
}
|
|
|
|
// is local var
|
|
auto index = variable_stack_index(ctx, expr);
|
|
|
|
switch(index)
|
|
{
|
|
case 0: emit_opcode(ctx, opcode::OP_EvalLocalVariableCached0); break;
|
|
case 1: emit_opcode(ctx, opcode::OP_EvalLocalVariableCached1); break;
|
|
case 2: emit_opcode(ctx, opcode::OP_EvalLocalVariableCached2); break;
|
|
case 3: emit_opcode(ctx, opcode::OP_EvalLocalVariableCached3); break;
|
|
case 4: emit_opcode(ctx, opcode::OP_EvalLocalVariableCached4); break;
|
|
case 5: emit_opcode(ctx, opcode::OP_EvalLocalVariableCached5); break;
|
|
default: emit_opcode(ctx, opcode::OP_EvalLocalVariableCached, variable_access_index(ctx, expr)); break;
|
|
}
|
|
}
|
|
|
|
void compiler::emit_clear_local_variable(const gsc::context_ptr& ctx, const gsc::name_ptr& expr)
|
|
{
|
|
auto index = variable_stack_index(ctx, expr);
|
|
|
|
if(index == 0)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ClearLocalVariableFieldCached0);
|
|
}
|
|
else
|
|
{
|
|
emit_opcode(ctx, opcode::OP_ClearLocalVariableFieldCached, variable_access_index(ctx, expr));
|
|
}
|
|
}
|
|
|
|
void compiler::emit_create_local_vars(const gsc::context_ptr& ctx)
|
|
{
|
|
if ( ctx->local_vars_create_count != ctx->local_vars_public_count )
|
|
{
|
|
for(auto i = ctx->local_vars_create_count; i < ctx->local_vars_public_count; i++)
|
|
{
|
|
auto data = utils::string::va("%d", ctx->local_vars.at(i).create);
|
|
emit_opcode(ctx, opcode::OP_CreateLocalVariable, data);
|
|
ctx->local_vars.at(i).init = true;
|
|
}
|
|
ctx->local_vars_create_count = ctx->local_vars_public_count;
|
|
}
|
|
}
|
|
|
|
void compiler::emit_remove_local_vars(const gsc::context_ptr& ctx)
|
|
{
|
|
if(ctx->abort == abort_t::abort_none)
|
|
{
|
|
auto count = ctx->local_vars_create_count - ctx->local_vars_public_count;
|
|
|
|
if(count > 0)
|
|
{
|
|
auto data = utils::string::va("%d", count);
|
|
emit_opcode(ctx, opcode::OP_RemoveLocalVariables, data);
|
|
}
|
|
}
|
|
}
|
|
|
|
void compiler::emit_object(const gsc::context_ptr& ctx, const gsc::expr_ptr& expr)
|
|
{
|
|
switch(expr.as_node->type)
|
|
{
|
|
case gsc::node_t::data_level:
|
|
emit_opcode(ctx, opcode::OP_GetLevelObject);
|
|
break;
|
|
case gsc::node_t::data_anim:
|
|
emit_opcode(ctx, opcode::OP_GetAnimObject);
|
|
break;
|
|
case gsc::node_t::data_self:
|
|
emit_opcode(ctx, opcode::OP_GetSelfObject);
|
|
break;
|
|
case gsc::node_t::data_name:
|
|
emit_opcode(ctx, opcode::OP_EvalLocalVariableObjectCached, variable_access_index(ctx, expr.as_name));
|
|
break;
|
|
case gsc::node_t::expr_call:
|
|
emit_expr_call(ctx, expr.as_call);
|
|
emit_opcode(ctx, opcode::OP_CastFieldObject);
|
|
break;
|
|
case gsc::node_t::expr_array:
|
|
emit_array_variable(ctx, expr.as_array);
|
|
emit_opcode(ctx, opcode::OP_CastFieldObject);
|
|
break;
|
|
case gsc::node_t::expr_field:
|
|
emit_field_variable(ctx, expr.as_field);
|
|
emit_opcode(ctx, opcode::OP_CastFieldObject);
|
|
break;
|
|
default:
|
|
throw gsc::comp_error(expr.as_node->loc, "unknown object type");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void compiler::emit_animtree(const gsc::context_ptr& ctx, const gsc::animtree_ptr& animtree)
|
|
{
|
|
if(animtrees_.size() == 0)
|
|
{
|
|
throw gsc::comp_error( animtree->loc, "trying to use animtree without specified using animtree");
|
|
}
|
|
|
|
auto& tree = animtrees_.back();
|
|
|
|
if(tree.loaded)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetAnimTree, "''");
|
|
}
|
|
else
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetAnimTree, tree.name);
|
|
tree.loaded = true;
|
|
}
|
|
}
|
|
|
|
void compiler::emit_animation(const gsc::context_ptr& ctx, const gsc::animation_ptr& animation)
|
|
{
|
|
if(animtrees_.size() == 0)
|
|
{
|
|
throw gsc::comp_error(animation->loc, "trying to use animation without specified using animtree");
|
|
}
|
|
|
|
auto& tree = animtrees_.back();
|
|
|
|
if(tree.loaded)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetAnimation, { "''", animation->value });
|
|
}
|
|
else
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetAnimation, { tree.name, animation->value });
|
|
tree.loaded = true;
|
|
}
|
|
}
|
|
|
|
void compiler::emit_istring(const gsc::context_ptr& ctx, const gsc::istring_ptr& str)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetIString, str->value);
|
|
}
|
|
|
|
void compiler::emit_string(const gsc::context_ptr& ctx, const gsc::string_ptr& str)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetString, str->value);
|
|
}
|
|
|
|
void compiler::emit_vector(const gsc::context_ptr& ctx, const gsc::vector_ptr& vec)
|
|
{
|
|
std::vector<std::string> data;
|
|
|
|
bool expr = false;
|
|
|
|
if(vec->x.as_node->type == gsc::node_t::data_integer)
|
|
data.push_back(vec->x.as_integer->value);
|
|
else if(vec->x.as_node->type == gsc::node_t::data_float)
|
|
data.push_back(vec->x.as_float->value);
|
|
else expr = true;
|
|
|
|
if(vec->y.as_node->type == gsc::node_t::data_integer)
|
|
data.push_back(vec->y.as_integer->value);
|
|
else if(vec->y.as_node->type == gsc::node_t::data_float)
|
|
data.push_back(vec->y.as_float->value);
|
|
else expr = true;
|
|
|
|
if(vec->z.as_node->type == gsc::node_t::data_integer)
|
|
data.push_back(vec->z.as_integer->value);
|
|
else if(vec->z.as_node->type == gsc::node_t::data_float)
|
|
data.push_back(vec->z.as_float->value);
|
|
else expr = true;
|
|
|
|
if(!expr)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetVector, data);
|
|
}
|
|
else
|
|
{
|
|
emit_expr(ctx, vec->z);
|
|
emit_expr(ctx, vec->y);
|
|
emit_expr(ctx, vec->x);
|
|
emit_opcode(ctx, opcode::OP_vector);
|
|
}
|
|
}
|
|
|
|
void compiler::emit_float(const gsc::context_ptr& ctx, const gsc::float_ptr& num)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetFloat, num->value);
|
|
}
|
|
|
|
void compiler::emit_integer(const gsc::context_ptr& ctx, const gsc::integer_ptr& num)
|
|
{
|
|
auto value = std::atoi(num->value.data());
|
|
|
|
if(value == 0)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetZero);
|
|
}
|
|
else if(value > 0 && value < 256)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetByte, num->value);
|
|
}
|
|
else if(value < 0 && value > -256)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetNegByte, num->value.substr(1));
|
|
}
|
|
else if(value > 0 && value < 65536)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetUnsignedShort, num->value);
|
|
}
|
|
else if(value < 0 && value > -65536)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetNegUnsignedShort, num->value.substr(1));
|
|
}
|
|
else
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetInteger, num->value);
|
|
}
|
|
}
|
|
|
|
void compiler::emit_false(const gsc::context_ptr& ctx, const gsc::false_ptr& expr)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetZero);
|
|
}
|
|
|
|
void compiler::emit_true(const gsc::context_ptr& ctx, const gsc::true_ptr& expr)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_GetByte, "1");
|
|
}
|
|
|
|
void compiler::emit_opcode(const gsc::context_ptr& ctx, opcode op)
|
|
{
|
|
function_->instructions.push_back(std::make_unique<gsc::instruction>());
|
|
|
|
auto& inst = function_->instructions.back();
|
|
inst->opcode = static_cast<std::uint8_t>(op);
|
|
inst->size = opcode_size(std::uint8_t(op));
|
|
inst->index = index_;
|
|
|
|
index_ += inst->size;
|
|
}
|
|
|
|
void compiler::emit_opcode(const gsc::context_ptr& ctx, opcode op, const std::string& data)
|
|
{
|
|
function_->instructions.push_back(std::make_unique<gsc::instruction>());
|
|
|
|
auto& inst = function_->instructions.back();
|
|
inst->opcode = static_cast<std::uint8_t>(op);
|
|
inst->size = opcode_size(std::uint8_t(op));
|
|
inst->index = index_;
|
|
inst->data.push_back(data);
|
|
|
|
index_ += inst->size;
|
|
}
|
|
|
|
void compiler::emit_opcode(const gsc::context_ptr& ctx, opcode op, const std::vector<std::string>& data)
|
|
{
|
|
function_->instructions.push_back(std::make_unique<gsc::instruction>());
|
|
|
|
auto& inst = function_->instructions.back();
|
|
inst->opcode = static_cast<std::uint8_t>(op);
|
|
inst->size = opcode_size(std::uint8_t(op));
|
|
inst->index = index_;
|
|
inst->data = data;
|
|
|
|
index_ += inst->size;
|
|
}
|
|
|
|
void compiler::process_thread(const gsc::context_ptr& ctx, const gsc::thread_ptr& thread)
|
|
{
|
|
process_parameters(ctx, thread->params);
|
|
process_stmt_list(ctx, thread->block);
|
|
}
|
|
|
|
void compiler::process_parameters(const gsc::context_ptr& ctx, const gsc::parameters_ptr& params)
|
|
{
|
|
for(const auto& param : params->list)
|
|
{
|
|
register_variable(ctx, param->value);
|
|
}
|
|
}
|
|
|
|
void compiler::process_stmt(const gsc::context_ptr& ctx, const gsc::stmt_ptr& stmt)
|
|
{
|
|
switch (stmt.as_node->type)
|
|
{
|
|
case gsc::node_t::stmt_list: process_stmt_list(ctx, stmt.as_list); break;
|
|
case gsc::node_t::stmt_assign: process_expr(ctx, stmt.as_assign->expr->lvalue); break;
|
|
case gsc::node_t::stmt_waittill: process_stmt_waittill(ctx, stmt.as_waittill); break;
|
|
case gsc::node_t::stmt_if: process_stmt_if(ctx, stmt.as_if); break;
|
|
case gsc::node_t::stmt_ifelse: process_stmt_ifelse(ctx, stmt.as_ifelse); break;
|
|
case gsc::node_t::stmt_while: process_stmt_while(ctx, stmt.as_while); break;
|
|
case gsc::node_t::stmt_for: process_stmt_for(ctx, stmt.as_for); break;
|
|
case gsc::node_t::stmt_foreach: process_stmt_foreach(ctx, stmt.as_foreach); break;
|
|
case gsc::node_t::stmt_switch: process_stmt_switch(ctx, stmt.as_switch); break;
|
|
case gsc::node_t::stmt_break: process_stmt_break(ctx, stmt.as_break); break;
|
|
case gsc::node_t::stmt_continue: process_stmt_continue(ctx, stmt.as_continue); break;
|
|
case gsc::node_t::stmt_return: process_stmt_return(ctx, stmt.as_return); break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
void compiler::process_stmt_list(const gsc::context_ptr& ctx, const gsc::stmt_list_ptr& stmt)
|
|
{
|
|
for (const auto& entry : stmt->stmts)
|
|
{
|
|
process_stmt(ctx, entry);
|
|
}
|
|
}
|
|
|
|
void compiler::process_expr(const gsc::context_ptr& ctx, const gsc::expr_ptr& expr)
|
|
{
|
|
if(expr.as_node->type == gsc::node_t::data_name)
|
|
{
|
|
register_variable(ctx, expr.as_name->value);
|
|
}
|
|
else if(expr.as_node->type == gsc::node_t::expr_array)
|
|
{
|
|
process_expr(ctx, expr.as_array->obj);
|
|
}
|
|
}
|
|
|
|
void compiler::process_stmt_waittill(const gsc::context_ptr& ctx, const gsc::stmt_waittill_ptr& stmt)
|
|
{
|
|
for(const auto& arg : stmt->args->list)
|
|
{
|
|
register_variable(ctx, arg.as_name->value);
|
|
}
|
|
}
|
|
|
|
void compiler::process_stmt_if(const gsc::context_ptr& ctx, const gsc::stmt_if_ptr& stmt)
|
|
{
|
|
stmt->ctx = std::make_unique<gsc::context>();
|
|
|
|
ctx->copy(stmt->ctx);
|
|
process_stmt(stmt->ctx, stmt->stmt);
|
|
|
|
std::vector<gsc::context*> childs({ stmt->ctx.get() });
|
|
ctx->merge(childs);
|
|
}
|
|
|
|
void compiler::process_stmt_ifelse(const gsc::context_ptr& ctx, const gsc::stmt_ifelse_ptr& stmt)
|
|
{
|
|
std::vector<gsc::context*> childs;
|
|
auto abort = abort_t::abort_return;
|
|
|
|
stmt->ctx_if = std::make_unique<gsc::context>();
|
|
stmt->ctx_else = std::make_unique<gsc::context>();
|
|
|
|
ctx->copy(stmt->ctx_if);
|
|
process_stmt(stmt->ctx_if, stmt->stmt_if);
|
|
|
|
if(stmt->ctx_if->abort <= abort_t::abort_return)
|
|
{
|
|
abort = stmt->ctx_if->abort;
|
|
if(abort == abort_t::abort_none)
|
|
childs.push_back(stmt->ctx_if.get());
|
|
}
|
|
|
|
ctx->copy(stmt->ctx_else);
|
|
process_stmt(stmt->ctx_else, stmt->stmt_else);
|
|
|
|
if(stmt->ctx_else->abort <= abort)
|
|
{
|
|
abort = stmt->ctx_else->abort;
|
|
if(abort == abort_t::abort_none)
|
|
childs.push_back(stmt->ctx_else.get());
|
|
}
|
|
|
|
if(ctx->abort == abort_t::abort_none)
|
|
ctx->abort = abort;
|
|
|
|
ctx->append(childs);
|
|
ctx->merge(childs);
|
|
}
|
|
|
|
void compiler::process_stmt_while(const gsc::context_ptr& ctx, const gsc::stmt_while_ptr& stmt)
|
|
{
|
|
bool const_cond = is_constant_condition(stmt->expr);
|
|
|
|
auto old_breaks = break_ctxs_;
|
|
auto old_continues = continue_ctxs_;
|
|
break_ctxs_.clear();
|
|
continue_ctxs_.clear();
|
|
|
|
stmt->ctx = std::make_unique<gsc::context>();
|
|
|
|
ctx->copy(stmt->ctx);
|
|
process_stmt(stmt->ctx, stmt->stmt);
|
|
|
|
continue_ctxs_.push_back(stmt->ctx.get());
|
|
|
|
for(auto i = 0; i < continue_ctxs_.size(); i++)
|
|
ctx->append({continue_ctxs_.at(i)});
|
|
|
|
if(const_cond) ctx->append(break_ctxs_);
|
|
|
|
ctx->merge({stmt->ctx.get()});
|
|
|
|
break_ctxs_ = old_breaks;
|
|
continue_ctxs_ = old_continues;
|
|
}
|
|
|
|
void compiler::process_stmt_for(const gsc::context_ptr& ctx, const gsc::stmt_for_ptr& stmt)
|
|
{
|
|
bool const_cond = is_constant_condition(stmt->expr);
|
|
|
|
stmt->ctx = std::make_unique<gsc::context>();
|
|
stmt->ctx_post = std::make_unique<gsc::context>();
|
|
|
|
process_stmt(ctx, stmt->pre_expr);
|
|
|
|
auto old_breaks = break_ctxs_;
|
|
auto old_continues = continue_ctxs_;
|
|
break_ctxs_.clear();
|
|
continue_ctxs_.clear();
|
|
|
|
ctx->copy(stmt->ctx);
|
|
ctx->copy(stmt->ctx_post);
|
|
|
|
process_stmt(stmt->ctx, stmt->stmt);
|
|
|
|
continue_ctxs_.push_back(stmt->ctx.get());
|
|
|
|
for(auto i = 0; i < continue_ctxs_.size(); i++)
|
|
ctx->append({continue_ctxs_.at(i)});
|
|
|
|
process_stmt(stmt->ctx_post, stmt->post_expr);
|
|
|
|
ctx->append({ stmt->ctx_post.get() });
|
|
ctx->merge({ stmt->ctx_post.get() });
|
|
|
|
if(const_cond) ctx->append(break_ctxs_);
|
|
|
|
ctx->merge({stmt->ctx.get()});
|
|
|
|
break_ctxs_ = old_breaks;
|
|
continue_ctxs_ = old_continues;
|
|
}
|
|
|
|
void compiler::process_stmt_foreach(const gsc::context_ptr& ctx, const gsc::stmt_foreach_ptr& stmt)
|
|
{
|
|
auto array_name = utils::string::va("temp_%d", ++label_idx_);
|
|
auto key_name = utils::string::va("temp_%d", ++label_idx_);
|
|
|
|
stmt->array = expr_ptr(std::make_unique<node_name>(stmt->loc, array_name));
|
|
|
|
if(!stmt->use_key)
|
|
stmt->key_expr = expr_ptr(std::make_unique<node_name>(stmt->loc, key_name));
|
|
|
|
key_name = stmt->key_expr.as_name->value;
|
|
|
|
// calculate variables
|
|
|
|
stmt->ctx = std::make_unique<gsc::context>();
|
|
stmt->ctx_post = std::make_unique<gsc::context>();
|
|
|
|
// calculate pre_expr variables
|
|
process_expr(ctx, stmt->array);
|
|
|
|
auto old_breaks = break_ctxs_;
|
|
auto old_continues = continue_ctxs_;
|
|
break_ctxs_.clear();
|
|
continue_ctxs_.clear();
|
|
|
|
ctx->copy(stmt->ctx);
|
|
ctx->copy(stmt->ctx_post);
|
|
|
|
// calculate stmt variables & add missing array access as first stmt
|
|
process_expr(stmt->ctx, stmt->value_expr);
|
|
process_stmt(stmt->ctx, stmt->stmt);
|
|
|
|
continue_ctxs_.push_back(stmt->ctx.get());
|
|
|
|
for(auto i = 0; i < continue_ctxs_.size(); i++)
|
|
ctx->append({continue_ctxs_.at(i)});
|
|
|
|
process_expr(stmt->ctx_post, stmt->key_expr);
|
|
|
|
ctx->append({ stmt->ctx_post.get() });
|
|
ctx->merge({ stmt->ctx_post.get() });
|
|
ctx->merge({stmt->ctx.get()});
|
|
|
|
break_ctxs_ = old_breaks;
|
|
continue_ctxs_ = old_continues;
|
|
}
|
|
|
|
void compiler::process_stmt_switch(const gsc::context_ptr& ctx, const gsc::stmt_switch_ptr& stmt)
|
|
{
|
|
auto stmt_list = std::make_unique<gsc::node_stmt_list>(stmt->stmt->loc);
|
|
auto current_case = gsc::stmt_ptr(std::make_unique<gsc::node>());
|
|
|
|
auto num = stmt->stmt->stmts.size();
|
|
|
|
for(auto i = 0; i < num; i++)
|
|
{
|
|
auto& entry = stmt->stmt->stmts[0];
|
|
|
|
if(entry.as_node->type == gsc::node_t::stmt_case || entry.as_node->type == gsc::node_t::stmt_default)
|
|
{
|
|
if(current_case.as_node->type != gsc::node_t::null)
|
|
{
|
|
stmt_list->stmts.push_back(std::move(current_case));
|
|
}
|
|
|
|
current_case = std::move(stmt->stmt->stmts[0]);
|
|
stmt->stmt->stmts.erase(stmt->stmt->stmts.begin());
|
|
}
|
|
else
|
|
{
|
|
if(current_case.as_node->type != gsc::node_t::null)
|
|
{
|
|
if(current_case.as_node->type == gsc::node_t::stmt_case)
|
|
{
|
|
current_case.as_case->stmt->stmts.push_back(std::move(entry));
|
|
stmt->stmt->stmts.erase(stmt->stmt->stmts.begin());
|
|
}
|
|
else
|
|
{
|
|
current_case.as_default->stmt->stmts.push_back(std::move(entry));
|
|
stmt->stmt->stmts.erase(stmt->stmt->stmts.begin());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gsc::comp_error(entry.as_node->loc, "missing case statement");
|
|
}
|
|
}
|
|
}
|
|
|
|
if(current_case.as_node->type != gsc::node_t::null)
|
|
{
|
|
stmt_list->stmts.push_back(std::move(current_case));
|
|
}
|
|
|
|
// calculate variables
|
|
stmt->ctx = std::make_unique<gsc::context>();
|
|
std::vector<gsc::context*> childs;
|
|
auto abort = abort_t::abort_return;
|
|
bool has_default = false;
|
|
gsc::context* default_ctx = nullptr;
|
|
auto old_breaks = break_ctxs_;
|
|
break_ctxs_.clear();
|
|
|
|
for(auto i = 0; i < stmt_list->stmts.size(); i++)
|
|
{
|
|
auto& entry = stmt_list->stmts[i];
|
|
|
|
if(entry.as_node->type == gsc::node_t::stmt_case)
|
|
{
|
|
entry.as_case->ctx = std::make_unique<gsc::context>();
|
|
ctx->copy(entry.as_case->ctx);
|
|
process_stmt_list(entry.as_case->ctx, entry.as_case->stmt);
|
|
|
|
if (entry.as_case->ctx->abort != abort_t::abort_none)
|
|
{
|
|
if (entry.as_case->ctx->abort == abort_t::abort_break )
|
|
{
|
|
entry.as_case->ctx->abort = abort_t::abort_none;
|
|
abort = abort_t::abort_none;
|
|
childs.push_back(entry.as_case->ctx.get());
|
|
}
|
|
else if (entry.as_case->ctx->abort <= abort )
|
|
{
|
|
abort = entry.as_case->ctx->abort;
|
|
}
|
|
}
|
|
}
|
|
else if(entry.as_node->type == gsc::node_t::stmt_default)
|
|
{
|
|
entry.as_default->ctx = std::make_unique<gsc::context>();
|
|
ctx->copy(entry.as_default->ctx);
|
|
process_stmt_list(entry.as_default->ctx, entry.as_default->stmt);
|
|
has_default = true;
|
|
default_ctx = entry.as_default->ctx.get();
|
|
|
|
if (entry.as_default->ctx->abort != abort_t::abort_none)
|
|
{
|
|
if (entry.as_default->ctx->abort == abort_t::abort_break )
|
|
{
|
|
entry.as_default->ctx->abort = abort_t::abort_none;
|
|
abort = abort_t::abort_none;
|
|
childs.push_back(entry.as_default->ctx.get());
|
|
}
|
|
else if (entry.as_default->ctx->abort <= abort )
|
|
{
|
|
abort = entry.as_default->ctx->abort;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
stmt->stmt =std::move(stmt_list);
|
|
|
|
if(has_default)
|
|
{
|
|
if(default_ctx->abort == abort_t::abort_none)
|
|
{
|
|
break_ctxs_.push_back(default_ctx);
|
|
|
|
if(ctx->abort == abort_t::abort_none)
|
|
ctx->abort = abort;
|
|
}
|
|
|
|
ctx->append(break_ctxs_);
|
|
ctx->merge(childs);
|
|
}
|
|
|
|
break_ctxs_ = old_breaks;
|
|
}
|
|
|
|
void compiler::process_stmt_break(const gsc::context_ptr& ctx, const gsc::stmt_break_ptr& stmt)
|
|
{
|
|
if(ctx->abort == abort_t::abort_none)
|
|
{
|
|
break_ctxs_.push_back(ctx.get());
|
|
ctx->abort = abort_t::abort_break;
|
|
}
|
|
}
|
|
|
|
void compiler::process_stmt_continue(const gsc::context_ptr& ctx, const gsc::stmt_continue_ptr& stmt)
|
|
{
|
|
if(ctx->abort == abort_t::abort_none)
|
|
{
|
|
continue_ctxs_.push_back(ctx.get());
|
|
ctx->abort = abort_t::abort_continue;
|
|
}
|
|
}
|
|
|
|
void compiler::process_stmt_return(const gsc::context_ptr& ctx, const gsc::stmt_return_ptr& stmt)
|
|
{
|
|
if(ctx->abort == abort_t::abort_none)
|
|
{
|
|
ctx->abort = abort_t::abort_return;
|
|
}
|
|
}
|
|
|
|
void compiler::register_variable(const gsc::context_ptr& ctx, const std::string& name)
|
|
{
|
|
auto it = std::find_if(ctx->local_vars.begin(), ctx->local_vars.end(),
|
|
[&](const gsc::local_var& v) { return v.name == name; });
|
|
|
|
if(it == ctx->local_vars.end())
|
|
{
|
|
auto found = false;
|
|
for(std::size_t i = 0; i < local_stack_.size(); i++)
|
|
{
|
|
if(local_stack_[i] == name)
|
|
{
|
|
ctx->local_vars.push_back({ name, static_cast<uint8_t>(i), false });
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!found)
|
|
{
|
|
ctx->local_vars.push_back({ name, stack_idx_, false });
|
|
local_stack_.push_back(name);
|
|
stack_idx_++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void compiler::initialize_variable(const gsc::context_ptr& ctx, const gsc::name_ptr& name)
|
|
{
|
|
for(std::uint32_t i = 0; i < ctx->local_vars.size(); i++)
|
|
{
|
|
if(ctx->local_vars[i].name == name->value)
|
|
{
|
|
if(!ctx->local_vars[i].init)
|
|
{
|
|
for(std::uint32_t j = 0; j < i; j++)
|
|
{
|
|
if(!ctx->local_vars[j].init)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_CreateLocalVariable, utils::string::va("%d", ctx->local_vars[j].create));
|
|
ctx->local_vars[j].init = true;
|
|
//ctx->local_vars_create_count++;
|
|
}
|
|
}
|
|
ctx->local_vars[i].init = true;
|
|
ctx->local_vars_create_count = i + 1;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
throw gsc::comp_error(name->loc, "local variable '" + name->value + "' not found.");
|
|
}
|
|
|
|
void compiler::create_variable(const gsc::context_ptr& ctx, const gsc::name_ptr& name)
|
|
{
|
|
for(std::size_t i = 0; i < ctx->local_vars.size(); i++)
|
|
{
|
|
auto& var = ctx->local_vars.at(i);
|
|
if(var.name == name->value)
|
|
{
|
|
if(!var.init)
|
|
{
|
|
emit_opcode(ctx, opcode::OP_CreateLocalVariable, utils::string::va("%d", var.create));
|
|
var.init = true;
|
|
ctx->local_vars_create_count++;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
throw gsc::comp_error(name->loc, "local variable '" + name->value + "' not found.");
|
|
}
|
|
|
|
auto compiler::variable_stack_index(const gsc::context_ptr& ctx, const gsc::name_ptr& name) -> std::uint8_t
|
|
{
|
|
for(std::size_t i = 0; i < ctx->local_vars.size(); i++)
|
|
{
|
|
if(ctx->local_vars[i].name == name->value)
|
|
{
|
|
if(ctx->local_vars.at(i).init)
|
|
{
|
|
return ctx->local_vars_create_count - 1 - i;
|
|
}
|
|
|
|
throw gsc::comp_error(name->loc, "local variable '" + name->value + "' not initialized.");
|
|
}
|
|
}
|
|
|
|
throw gsc::comp_error(name->loc, "local variable '" + name->value + "' not found.");
|
|
}
|
|
|
|
auto compiler::variable_create_index(const gsc::context_ptr& ctx, const gsc::name_ptr& name) -> std::string
|
|
{
|
|
for(std::size_t i = 0; i < ctx->local_vars.size(); i++)
|
|
{
|
|
if(ctx->local_vars[i].name == name->value)
|
|
return utils::string::va("%d", ctx->local_vars[i].create);
|
|
}
|
|
|
|
throw gsc::comp_error(name->loc, "local variable '" + name->value + "' not found.");
|
|
}
|
|
|
|
auto compiler::variable_access_index(const gsc::context_ptr& ctx, const gsc::name_ptr& name) -> std::string
|
|
{
|
|
for(std::size_t i = 0; i < ctx->local_vars.size(); i++)
|
|
{
|
|
if(ctx->local_vars[i].name == name->value)
|
|
{
|
|
if(ctx->local_vars.at(i).init)
|
|
{
|
|
return utils::string::va("%d", ctx->local_vars_create_count - 1 - i);
|
|
}
|
|
|
|
throw gsc::comp_error(name->loc, "local variable '" + name->value + "' not initialized.");
|
|
}
|
|
}
|
|
|
|
throw gsc::comp_error(name->loc, "local variable '" + name->value + "' not found.");
|
|
}
|
|
|
|
auto compiler::variable_initialized(const gsc::context_ptr& ctx, const gsc::name_ptr& name) -> bool
|
|
{
|
|
for(std::size_t i = 0; i < ctx->local_vars.size(); i++)
|
|
{
|
|
if(ctx->local_vars[i].name == name->value)
|
|
{
|
|
return ctx->local_vars.at(i).init;
|
|
}
|
|
}
|
|
|
|
throw gsc::comp_error(name->loc, "local variable '" + name->value + "' not found.");
|
|
}
|
|
|
|
auto compiler::is_include_call(const std::string& name, std::string& file) -> bool
|
|
{
|
|
for(const auto& inc : includes_)
|
|
{
|
|
for(const auto& fun : inc.funcs)
|
|
{
|
|
if(name == fun)
|
|
{
|
|
file = inc.name;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
auto compiler::is_local_call(const std::string& name) -> bool
|
|
{
|
|
for(const auto& f : local_functions_)
|
|
{
|
|
if(f == name) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
auto compiler::is_builtin_call(const std::string& name) -> bool
|
|
{
|
|
if(is_builtin_func(name)) return true;
|
|
|
|
if(is_builtin_method(name)) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
auto compiler::is_builtin_func(const std::string& name) -> bool
|
|
{
|
|
return resolver::find_function(name);
|
|
}
|
|
auto compiler::is_builtin_method(const std::string& name) -> bool
|
|
{
|
|
return resolver::find_method(name);
|
|
}
|
|
|
|
auto compiler::is_constant_condition(const gsc::expr_ptr& expr) -> bool
|
|
{
|
|
switch(expr.as_node->type)
|
|
{
|
|
case gsc::node_t::null:
|
|
case gsc::node_t::data_true:
|
|
return true;
|
|
case gsc::node_t::data_false:
|
|
throw gsc::comp_error(expr.as_node->loc, "condition can't be always false!");
|
|
case gsc::node_t::data_integer:
|
|
{
|
|
auto num = std::stoi(expr.as_integer->value);
|
|
if(num != 0)
|
|
return true;
|
|
else
|
|
throw gsc::comp_error(expr.as_node->loc, "condition can't be always false!");
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
auto compiler::create_label() -> std::string
|
|
{
|
|
label_idx_++;
|
|
auto name = utils::string::va("loc_%d", label_idx_);
|
|
return name;
|
|
}
|
|
|
|
auto compiler::insert_label() -> std::string
|
|
{
|
|
const auto itr = function_->labels.find(index_);
|
|
|
|
if (itr != function_->labels.end())
|
|
{
|
|
return itr->second;
|
|
}
|
|
else
|
|
{
|
|
label_idx_++;
|
|
auto name = utils::string::va("loc_%d", label_idx_);
|
|
function_->labels.insert({index_, name});
|
|
return name;
|
|
}
|
|
}
|
|
|
|
void compiler::insert_label(const std::string& name)
|
|
{
|
|
const auto itr = function_->labels.find(index_);
|
|
|
|
if (itr != function_->labels.end())
|
|
{
|
|
for(auto& inst : function_->instructions)
|
|
{
|
|
switch (opcode(inst->opcode))
|
|
{
|
|
case opcode::OP_JumpOnFalse:
|
|
case opcode::OP_JumpOnTrue:
|
|
case opcode::OP_JumpOnFalseExpr:
|
|
case opcode::OP_JumpOnTrueExpr:
|
|
case opcode::OP_jump:
|
|
case opcode::OP_jumpback:
|
|
case opcode::OP_switch:
|
|
if(inst->data[0] == name)
|
|
inst->data[0] = itr->second;
|
|
break;
|
|
case opcode::OP_endswitch:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
function_->labels.insert({index_, name});
|
|
}
|
|
}
|
|
|
|
|
|
gsc::include_t compiler::include_maps_mp_utility_ =
|
|
{
|
|
"maps/mp/_utility",
|
|
{
|
|
"exploder_sound",
|
|
"_beginlocationselection",
|
|
"stoplocationselection",
|
|
"endselectiononemp",
|
|
"endselectiononaction",
|
|
"endselectiononendgame",
|
|
"isattachment",
|
|
"getattachmenttype",
|
|
"delaythread",
|
|
"delaythread_proc",
|
|
"getplant",
|
|
"orienttonormal",
|
|
"deleteplacedentity",
|
|
"playsoundonplayers",
|
|
"sortlowermessages",
|
|
"addlowermessage",
|
|
"removelowermessage",
|
|
"getlowermessage",
|
|
"setlowermessage",
|
|
"updatelowermessage",
|
|
"clearondeath",
|
|
"clearafterfade",
|
|
"clearlowermessage",
|
|
"clearlowermessages",
|
|
"printonteam",
|
|
"printboldonteam",
|
|
"printboldonteamarg",
|
|
"printonteamarg",
|
|
"printonplayers",
|
|
"printandsoundoneveryone",
|
|
"printandsoundonteam",
|
|
"printandsoundonplayer",
|
|
"_playlocalsound",
|
|
"dvarintvalue",
|
|
"dvarfloatvalue",
|
|
"play_sound_on_tag",
|
|
"getotherteam",
|
|
"wait_endon",
|
|
"initpersstat",
|
|
"getpersstat",
|
|
"incpersstat",
|
|
"setpersstat",
|
|
"initplayerstat",
|
|
"incplayerstat",
|
|
"setplayerstat",
|
|
"getplayerstat",
|
|
"getplayerstattime",
|
|
"setplayerstatifgreater",
|
|
"setplayerstatiflower",
|
|
"updatepersratio",
|
|
"updatepersratiobuffered",
|
|
"waittillslowprocessallowed",
|
|
"waitfortimeornotify",
|
|
"isexcluded",
|
|
"leaderdialog",
|
|
"leaderdialogbothteams",
|
|
"leaderdialogonplayers",
|
|
"leaderdialogonplayer",
|
|
"playleaderdialogonplayer",
|
|
"updatemainmenu",
|
|
"updateobjectivetext",
|
|
"setobjectivetext",
|
|
"setobjectivescoretext",
|
|
"setobjectivehinttext",
|
|
"getobjectivetext",
|
|
"getobjectivescoretext",
|
|
"getobjectivehinttext",
|
|
"gettimepassed",
|
|
"getsecondspassed",
|
|
"getminutespassed",
|
|
"clearkillcamstate",
|
|
"isinkillcam",
|
|
"isvalidclass",
|
|
"getvalueinrange",
|
|
"waitfortimeornotifies",
|
|
"closemenus",
|
|
"logxpgains",
|
|
"registerroundswitchdvar",
|
|
"registerroundlimitdvar",
|
|
"registerwinlimitdvar",
|
|
"registerscorelimitdvar",
|
|
"registertimelimitdvar",
|
|
"registerhalftimedvar",
|
|
"registernumlivesdvar",
|
|
"setovertimelimitdvar",
|
|
"get_damageable_player",
|
|
"get_damageable_sentry",
|
|
"get_damageable_grenade",
|
|
"get_damageable_mine",
|
|
"get_damageable_player_pos",
|
|
"getstancecenter",
|
|
"get_damageable_grenade_pos",
|
|
"getdvarvec",
|
|
"strip_suffix",
|
|
"_takeweaponsexcept",
|
|
"savedata",
|
|
"restoredata",
|
|
"_setactionslot",
|
|
"isfloat",
|
|
"registerwatchdvarint",
|
|
"registerwatchdvarfloat",
|
|
"registerwatchdvar",
|
|
"setoverridewatchdvar",
|
|
"getwatcheddvar",
|
|
"updatewatcheddvars",
|
|
"isroundbased",
|
|
"islastround",
|
|
"wasonlyround",
|
|
"waslastround",
|
|
"hitroundlimit",
|
|
"hitscorelimit",
|
|
"hitwinlimit",
|
|
"getscorelimit",
|
|
"getroundswon",
|
|
"isobjectivebased",
|
|
"gettimelimit",
|
|
"gethalftime",
|
|
"inovertime",
|
|
"gamehasstarted",
|
|
"getaverageorigin",
|
|
"getlivingplayers",
|
|
"setusingremote",
|
|
"getremotename",
|
|
"freezecontrolswrapper",
|
|
"clearusingremote",
|
|
"isusingremote",
|
|
"queuecreate",
|
|
"queueadd",
|
|
"queueremovefirst",
|
|
"_giveweapon",
|
|
"_hasperk",
|
|
"giveperk",
|
|
"_setperk",
|
|
"_setextraperks",
|
|
"_unsetperk",
|
|
"_unsetextraperks",
|
|
"_clearperks",
|
|
"quicksort",
|
|
"quicksortmid",
|
|
"swap",
|
|
"_suicide",
|
|
"isreallyalive",
|
|
"playdeathsound",
|
|
"rankingenabled",
|
|
"privatematch",
|
|
"matchmakinggame",
|
|
"setaltsceneobj",
|
|
"endsceneondeath",
|
|
"getgametypenumlives",
|
|
"givecombathigh",
|
|
"arrayinsertion",
|
|
"getproperty",
|
|
"getintproperty",
|
|
"getfloatproperty",
|
|
"statusmenu",
|
|
"ischangingweapon",
|
|
"killshouldaddtokillstreak",
|
|
"streakshouldchain",
|
|
"isjuggernaut",
|
|
"iskillstreakweapon",
|
|
"isenvironmentweapon",
|
|
"getweaponclass",
|
|
"isdeathstreakweapon",
|
|
"getbaseweaponname",
|
|
"fixakimbostring",
|
|
"playsoundinspace",
|
|
"limitdecimalplaces",
|
|
"rounddecimalplaces",
|
|
"playerforclientid",
|
|
"isrested",
|
|
"stringtofloat",
|
|
"setselfusable",
|
|
"maketeamusable",
|
|
"_updateteamusable",
|
|
"makeenemyusable",
|
|
"_updateenemyusable",
|
|
"getnextlifeid",
|
|
"initgameflags",
|
|
"gameflaginit",
|
|
"gameflag",
|
|
"gameflagset",
|
|
"gameflagclear",
|
|
"gameflagwait",
|
|
"isprimarydamage",
|
|
"isbulletdamage",
|
|
"initlevelflags",
|
|
"levelflaginit",
|
|
"levelflag",
|
|
"levelflagset",
|
|
"levelflagclear",
|
|
"levelflagwait",
|
|
"levelflagwaitopen",
|
|
"getweaponattachments",
|
|
"isemped",
|
|
"isairdenied",
|
|
"isnuked",
|
|
"getplayerforguid",
|
|
"teamplayercardsplash",
|
|
"iscacprimaryweapon",
|
|
"iscacsecondaryweapon",
|
|
"getlastlivingplayer",
|
|
"getpotentiallivingplayers",
|
|
"waittillrecoveredhealth",
|
|
"attachmentmap",
|
|
"validateattachment",
|
|
"_objective_delete",
|
|
"touchingbadtrigger",
|
|
"setthirdpersondof",
|
|
"killtrigger",
|
|
"findisfacing",
|
|
"combinearrays",
|
|
"setrecoilscale",
|
|
"cleanarray",
|
|
"notusableforjoiningplayers",
|
|
"isstrstart",
|
|
"validateusestreak",
|
|
"currentactivevehiclecount",
|
|
"maxvehiclesallowed",
|
|
"incrementfauxvehiclecount",
|
|
"decrementfauxvehiclecount",
|
|
"lightweightscalar",
|
|
"allowteamchoice",
|
|
"allowclasschoice",
|
|
"isbuffunlockedforweapon",
|
|
"isbuffequippedonweapon",
|
|
"setcommonrulesfrommatchrulesdata",
|
|
"reinitializematchrulesonmigration",
|
|
"getmatchrulesspecialclass",
|
|
"recipeclassapplyjuggernaut",
|
|
}
|
|
};
|
|
|
|
gsc::include_t compiler::include_common_scripts_createfx_ =
|
|
{
|
|
"common_scripts/_createfx",
|
|
{
|
|
"createeffect",
|
|
"getloopeffectdelaydefault",
|
|
"getoneshoteffectdelaydefault",
|
|
"getexploderdelaydefault",
|
|
"getintervalsounddelaymindefault",
|
|
"getintervalsounddelaymaxdefault",
|
|
"add_effect",
|
|
"createloopsound",
|
|
"createintervalsound",
|
|
"createnewexploder",
|
|
"createexploderex",
|
|
"set_origin_and_angles",
|
|
"set_forward_and_up_vectors",
|
|
"createfx_common",
|
|
"createfxlogic",
|
|
"copy_angles_of_selected_ents",
|
|
"reset_axis_of_selected_ents",
|
|
"last_selected_entity_has_changed",
|
|
"createfx_showorigin",
|
|
"drop_selection_to_ground",
|
|
"set_off_exploders",
|
|
"draw_distance",
|
|
"createfx_autosave",
|
|
"rotate_over_time",
|
|
"delete_pressed",
|
|
"remove_selected_option",
|
|
"remove_option",
|
|
"delete_selection",
|
|
"move_selection_to_cursor",
|
|
"insert_effect",
|
|
"show_help",
|
|
"select_last_entity",
|
|
"select_all_exploders_of_currently_selected",
|
|
"copy_ents",
|
|
"post_entity_creation_function",
|
|
"paste_ents",
|
|
"add_and_select_entity",
|
|
"get_center_of_array",
|
|
"ent_draw_axis",
|
|
"rotation_is_occuring",
|
|
"print_fx_options",
|
|
"entity_highlight_disable",
|
|
"entity_highlight_enable",
|
|
"toggle_createfx_drawing",
|
|
"manipulate_createfx_ents",
|
|
"clear_settable_fx",
|
|
"reset_fx_hud_colors",
|
|
"button_is_held",
|
|
"button_is_clicked",
|
|
"toggle_entity_selection",
|
|
"select_entity",
|
|
"ent_is_highlighted",
|
|
"deselect_entity",
|
|
"index_is_selected",
|
|
"ent_is_selected",
|
|
"clear_entity_selection",
|
|
"draw_axis",
|
|
"clear_fx_hudelements",
|
|
"set_fx_hudelement",
|
|
"createfx_centerprint",
|
|
"createfx_centerprint_thread",
|
|
"buttondown",
|
|
"buttonpressed_internal",
|
|
"get_selected_move_vector",
|
|
"process_button_held_and_clicked",
|
|
"locked",
|
|
"kb_locked",
|
|
"add_button",
|
|
"add_kb_button",
|
|
"set_anglemod_move_vector",
|
|
"cfxprintlnstart",
|
|
"cfxprintln",
|
|
"cfxprintlnend",
|
|
"update_selected_entities",
|
|
"hack_start",
|
|
"get_player",
|
|
"createfx_orgranize_array",
|
|
"stop_fx_looper",
|
|
"stop_loopsound",
|
|
"func_get_level_fx",
|
|
"restart_fx_looper",
|
|
"process_fx_rotater",
|
|
"generate_fx_log",
|
|
}
|
|
};
|
|
|
|
gsc::include_t compiler::include_common_scripts_utility_ =
|
|
{
|
|
"common_scripts/utility",
|
|
{
|
|
"scriptprintln",
|
|
"debugprintln",
|
|
"draw_debug_line",
|
|
"waittillend",
|
|
"noself_func",
|
|
"self_func",
|
|
"randomvector",
|
|
"randomvectorrange",
|
|
"angle_dif",
|
|
"sign",
|
|
"track",
|
|
"get_enemy_team",
|
|
"clear_exception",
|
|
"set_exception",
|
|
"set_all_exceptions",
|
|
"cointoss",
|
|
"choose_from_weighted_array",
|
|
"get_cumulative_weights",
|
|
"waittill_string",
|
|
"waittill_multiple",
|
|
"waittill_multiple_ents",
|
|
"waittill_any_return",
|
|
"waittill_any_timeout",
|
|
"_timeout",
|
|
"waittill_any",
|
|
"waittill_any_ents",
|
|
"isflashed",
|
|
"flag_exist",
|
|
"flag",
|
|
"init_flags",
|
|
"flag_init",
|
|
"empty_init_func",
|
|
"issuffix",
|
|
"flag_set",
|
|
"assign_unique_id",
|
|
"flag_wait",
|
|
"flag_clear",
|
|
"flag_waitopen",
|
|
"waittill_either",
|
|
"array_thread",
|
|
"array_call",
|
|
"array_thread4",
|
|
"array_thread5",
|
|
"trigger_on",
|
|
"trigger_on_proc",
|
|
"trigger_off",
|
|
"trigger_off_proc",
|
|
"set_trigger_flag_permissions",
|
|
"update_trigger_based_on_flags",
|
|
"create_flags_and_return_tokens",
|
|
"init_trigger_flags",
|
|
"getstruct",
|
|
"getstructarray",
|
|
"struct_class_init",
|
|
"fileprint_start",
|
|
"fileprint_map_start",
|
|
"fileprint_map_header",
|
|
"fileprint_map_keypairprint",
|
|
"fileprint_map_entity_start",
|
|
"fileprint_map_entity_end",
|
|
"fileprint_radiant_vec",
|
|
"array_remove",
|
|
"array_remove_array",
|
|
"array_removeundefined",
|
|
"array_levelthread",
|
|
"array_levelcall",
|
|
"add_to_array",
|
|
"flag_assert",
|
|
"flag_wait_either",
|
|
"flag_wait_either_return",
|
|
"flag_wait_any",
|
|
"flag_wait_any_return",
|
|
"flag_wait_all",
|
|
"flag_wait_or_timeout",
|
|
"flag_waitopen_or_timeout",
|
|
"wait_for_flag_or_time_elapses",
|
|
"delaycall",
|
|
"delaycall_proc",
|
|
"noself_delaycall",
|
|
"noself_delaycall_proc",
|
|
"issp",
|
|
"issp_towerdefense",
|
|
"string_starts_with",
|
|
"plot_points",
|
|
"draw_line_for_time",
|
|
"array_combine",
|
|
"flat_angle",
|
|
"flat_origin",
|
|
"draw_arrow_time",
|
|
"get_linked_ents",
|
|
"get_linked_vehicle_nodes",
|
|
"get_linked_ent",
|
|
"get_linked_vehicle_node",
|
|
"get_links",
|
|
"run_thread_on_targetname",
|
|
"run_thread_on_noteworthy",
|
|
"draw_arrow",
|
|
"getfx",
|
|
"fxexists",
|
|
"print_csv_asset",
|
|
"fileprint_csv_start",
|
|
"_loadfx",
|
|
"getlastweapon",
|
|
"playerunlimitedammothread",
|
|
"isusabilityenabled",
|
|
"_disableusability",
|
|
"_enableusability",
|
|
"resetusability",
|
|
"_disableweapon",
|
|
"_enableweapon",
|
|
"isweaponenabled",
|
|
"_disableweaponswitch",
|
|
"_enableweaponswitch",
|
|
"isweaponswitchenabled",
|
|
"_disableoffhandweapons",
|
|
"_enableoffhandweapons",
|
|
"isoffhandweaponenabled",
|
|
"random",
|
|
"spawn_tag_origin",
|
|
"waittill_notify_or_timeout",
|
|
"fileprint_launcher_start_file",
|
|
"fileprint_launcher",
|
|
"fileprint_launcher_end_file",
|
|
"launcher_write_clipboard",
|
|
"isdestructible",
|
|
"pauseeffect",
|
|
"activate_individual_exploder",
|
|
"waitframe",
|
|
"brush_delete",
|
|
"brush_throw",
|
|
"get_target_ent",
|
|
"brush_show",
|
|
"exploder_earthquake",
|
|
"do_earthquake",
|
|
"exploder_rumble",
|
|
"exploder_delay",
|
|
"exploder_damage",
|
|
"effect_loopsound",
|
|
"play_loopsound_in_space",
|
|
"sound_effect",
|
|
"effect_soundalias",
|
|
"play_sound_in_space",
|
|
"cannon_effect",
|
|
"exploder_playsound",
|
|
"fire_effect",
|
|
"loop_fx_sound",
|
|
"loop_fx_sound_interval",
|
|
"loop_sound_delete",
|
|
"exploder_before_load",
|
|
"exploder_after_load",
|
|
"activate_exploder",
|
|
"createloopeffect",
|
|
"createoneshoteffect",
|
|
"createexploder",
|
|
"alphabetize",
|
|
"is_later_in_alphabet",
|
|
"alphabet_compare",
|
|
"play_loop_sound_on_entity",
|
|
"stop_loop_sound_on_entity",
|
|
"delete_on_death",
|
|
"error",
|
|
"exploder",
|
|
"create_dvar",
|
|
"void",
|
|
"tag_project",
|
|
"ter_op",
|
|
"create_lock",
|
|
"lock",
|
|
"is_locked",
|
|
"unlock_wait",
|
|
"unlock",
|
|
"unlock_thread",
|
|
"get_template_level",
|
|
}
|
|
};
|
|
|
|
gsc::include_t compiler::include_maps_mp_gametypes_hud_util_ =
|
|
{
|
|
"maps/mp/gametypes/_hud_util",
|
|
{
|
|
"setparent",
|
|
"getparent",
|
|
"addchild",
|
|
"removechild",
|
|
"setpoint",
|
|
"setpointbar",
|
|
"updatebar",
|
|
"updatebarscale",
|
|
"createfontstring",
|
|
"createserverfontstring",
|
|
"createservertimer",
|
|
"createtimer",
|
|
"createicon",
|
|
"createservericon",
|
|
"createserverbar",
|
|
"createbar",
|
|
"getcurrentfraction",
|
|
"createprimaryprogressbar",
|
|
"createprimaryprogressbartext",
|
|
"createteamprogressbar",
|
|
"createteamprogressbartext",
|
|
"setflashfrac",
|
|
"hideelem",
|
|
"showelem",
|
|
"flashthread",
|
|
"destroyelem",
|
|
"seticonshader",
|
|
"geticonshader",
|
|
"seticonsize",
|
|
"setwidth",
|
|
"setheight",
|
|
"setsize",
|
|
"updatechildren",
|
|
"transitionreset",
|
|
"transitionzoomin",
|
|
"transitionpulsefxin",
|
|
"transitionslidein",
|
|
"transitionslideout",
|
|
"transitionzoomout",
|
|
"transitionfadein",
|
|
"transitionfadeout",
|
|
"getweeklyref",
|
|
"getdailyref",
|
|
"ch_getprogress",
|
|
"ch_getstate",
|
|
"ch_setprogress",
|
|
"ch_setstate",
|
|
"ch_gettarget",
|
|
}
|
|
};
|
|
|
|
auto compiler::map_known_includes(const std::string& include) -> bool
|
|
{
|
|
if(include == "maps/mp/_utility")
|
|
{
|
|
includes_.push_back(include_maps_mp_utility_);
|
|
return true;
|
|
}
|
|
else if(include == "common_scripts/utility")
|
|
{
|
|
includes_.push_back(include_common_scripts_utility_);
|
|
return true;
|
|
}
|
|
else if(include == "common_scripts/_createfx")
|
|
{
|
|
includes_.push_back(include_common_scripts_createfx_);
|
|
return true;
|
|
}
|
|
else if(include == "maps/mp/gametypes/_hud_util")
|
|
{
|
|
includes_.push_back(include_maps_mp_gametypes_hud_util_);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void compiler::print_debug_info()
|
|
{
|
|
printf("----------------------------------\n");
|
|
printf("files included: %zu\n", includes_.size());
|
|
printf("animtrees used: %zu\n", animtrees_.size());
|
|
printf("functions compiled: %zu\n",assembly_.size());
|
|
|
|
for (auto& func : assembly_)
|
|
{
|
|
this->print_function(func);
|
|
|
|
for (auto& inst : func->instructions)
|
|
{
|
|
const auto itr = func->labels.find(inst->index);
|
|
|
|
if (itr != func->labels.end())
|
|
{
|
|
this->print_label(itr->second);
|
|
}
|
|
|
|
this->print_instruction(inst);
|
|
}
|
|
}
|
|
|
|
printf("----------------------------------\n");
|
|
}
|
|
|
|
void compiler::print_opcodes(std::uint32_t index, std::uint32_t size)
|
|
{
|
|
printf(" ");
|
|
}
|
|
|
|
void compiler::print_function(const gsc::function_ptr& func)
|
|
{
|
|
printf("\n");
|
|
printf("%s\n", func->name.data());
|
|
}
|
|
|
|
void compiler::print_instruction(const gsc::instruction_ptr& inst)
|
|
{
|
|
switch (opcode(inst->opcode))
|
|
{
|
|
case opcode::OP_endswitch:
|
|
this->print_opcodes(inst->index, 3);
|
|
printf("%s", resolver::opcode_name(inst->opcode).data());
|
|
printf(" %s\n", inst->data[0].data());
|
|
{
|
|
std::uint32_t totalcase = std::stoul(inst->data[0]);
|
|
auto index = 0;
|
|
for (auto casenum = 0u; casenum < totalcase; casenum++)
|
|
{
|
|
this->print_opcodes(inst->index, 7);
|
|
if (inst->data[1 + index] == "case")
|
|
{
|
|
printf("%s %s %s", inst->data[1 + index].data(), inst->data[1 + index + 1].data(), inst->data[1 + index + 2].data());
|
|
index += 3;
|
|
}
|
|
else if (inst->data[1 + index] == "default")
|
|
{
|
|
printf("%s %s", inst->data[1 + index].data(), inst->data[1 + index + 1].data());
|
|
index += 2;
|
|
}
|
|
if (casenum != totalcase - 1)
|
|
{
|
|
printf("\n");
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
this->print_opcodes(inst->index, inst->size);
|
|
printf("%s", resolver::opcode_name(inst->opcode).data());
|
|
for (auto& d : inst->data)
|
|
{
|
|
printf(" %s", d.data());
|
|
}
|
|
break;
|
|
}
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
void compiler::print_label(const std::string& label)
|
|
{
|
|
printf(" %s\n", label.data());
|
|
}
|
|
|
|
} // namespace xsk::gsc::iw5
|