mirror of
https://github.com/ton-blockchain/ton
synced 2025-03-09 15:40:10 +00:00
[Tolk] Tolk v0.5.0 as FunC v0.5.0 could have been like
All changes from PR "FunC v0.5.0": https://github.com/ton-blockchain/ton/pull/1026 Instead of developing FunC, we decided to fork it. BTW, the first Tolk release will be v0.6, a metaphor of FunC v0.5 that missed a chance to occur.
This commit is contained in:
parent
82648ebd6a
commit
ebbab54cda
21 changed files with 1345 additions and 789 deletions
|
@ -81,14 +81,14 @@ int fixed248::acot(int x) inline_ref;
|
|||
|
||||
;; random number uniformly distributed in [0..1)
|
||||
;; fixed248 random();
|
||||
int fixed248::random() impure inline;
|
||||
int fixed248::random() inline;
|
||||
;; random number with standard normal distribution (2100 gas on average)
|
||||
;; fixed248 nrand();
|
||||
int fixed248::nrand() impure inline;
|
||||
int fixed248::nrand() inline;
|
||||
;; generates a random number approximately distributed according to the standard normal distribution (1200 gas)
|
||||
;; (fails chi-squared test, but it is shorter and faster than fixed248::nrand())
|
||||
;; fixed248 nrand_fast();
|
||||
int fixed248::nrand_fast() impure inline;
|
||||
int fixed248::nrand_fast() inline;
|
||||
|
||||
-} ;; end (declarations)
|
||||
|
||||
|
@ -880,7 +880,7 @@ int fixed248::acot(int x) inline_ref {
|
|||
;; generated by Kinderman--Monahan ratio method modified by J.Leva
|
||||
;; spends ~ 2k..3k gas on average
|
||||
;; fixed252 nrand();
|
||||
int nrand_f252() impure inline_ref {
|
||||
int nrand_f252() inline_ref {
|
||||
var (x, s, t, A, B, r0) = (nan(), touch(29483) << 236, touch(-3167) << 239, 12845, 16693, 9043);
|
||||
;; 4/sqrt(e*Pi) = 1.369 loop iterations on average
|
||||
do {
|
||||
|
@ -910,7 +910,7 @@ int nrand_f252() impure inline_ref {
|
|||
;; generates a random number approximately distributed according to the standard normal distribution
|
||||
;; much faster than nrand_f252(), should be suitable for most purposes when only several random numbers are needed
|
||||
;; fixed252 nrand_fast();
|
||||
int nrand_fast_f252() impure inline_ref {
|
||||
int nrand_fast_f252() inline_ref {
|
||||
int t = touch(-3) << 253; ;; -6. as fixed252
|
||||
repeat (12) {
|
||||
t += random() / 16; ;; add together 12 uniformly random numbers
|
||||
|
@ -920,18 +920,18 @@ int nrand_fast_f252() impure inline_ref {
|
|||
|
||||
;; random number uniformly distributed in [0..1)
|
||||
;; fixed248 random();
|
||||
int fixed248::random() impure inline {
|
||||
int fixed248::random() inline {
|
||||
return random() >> 8;
|
||||
}
|
||||
|
||||
;; random number with standard normal distribution
|
||||
;; fixed248 nrand();
|
||||
int fixed248::nrand() impure inline {
|
||||
int fixed248::nrand() inline {
|
||||
return nrand_f252() ~>> 4;
|
||||
}
|
||||
|
||||
;; generates a random number approximately distributed according to the standard normal distribution
|
||||
;; fixed248 nrand_fast();
|
||||
int fixed248::nrand_fast() impure inline {
|
||||
int fixed248::nrand_fast() inline {
|
||||
return nrand_fast_f252() ~>> 4;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -926,7 +926,7 @@ bool TestNode::show_help(std::string command) {
|
|||
"saveaccount[code|data] <filename> <addr> [<block-id-ext>]\tSaves into specified file the most recent state "
|
||||
"(StateInit) or just the code or data of specified account; <addr> is in "
|
||||
"[<workchain>:]<hex-or-base64-addr> format\n"
|
||||
"runmethod[full] <addr> [<block-id-ext>] <method-id> <params>...\tRuns GET method <method-id> of account "
|
||||
"runmethod[full] <addr> [<block-id-ext>] <name> <params>...\tRuns GET method <name> of account "
|
||||
"<addr> "
|
||||
"with specified parameters\n"
|
||||
"dnsresolve [<block-id-ext>] <domain> [<category>]\tResolves a domain starting from root dns smart contract\n"
|
||||
|
|
|
@ -24,6 +24,10 @@ target_link_libraries(tolk PUBLIC git ton_crypto) # todo replace with ton_crypt
|
|||
if (WINGETOPT_FOUND)
|
||||
target_link_libraries_system(tolk wingetopt)
|
||||
endif ()
|
||||
if (${TOLK_DEBUG}) # -DTOLK_DEBUG=1 in CMake options => #define TOLK_DEBUG (for development purposes)
|
||||
message(STATUS "TOLK_DEBUG is ON")
|
||||
target_compile_definitions(tolk PRIVATE TOLK_DEBUG=1)
|
||||
endif()
|
||||
|
||||
if (USE_EMSCRIPTEN)
|
||||
add_executable(tolkfiftlib tolk-wasm.cpp ${TOLK_SOURCE})
|
||||
|
|
|
@ -221,15 +221,6 @@ void VarDescrList::show(std::ostream& os) const {
|
|||
os << " ]\n";
|
||||
}
|
||||
|
||||
void Op::flags_set_clear(int set, int clear) {
|
||||
flags = (flags | set) & ~clear;
|
||||
for (auto& op : block0) {
|
||||
op.flags_set_clear(set, clear);
|
||||
}
|
||||
for (auto& op : block1) {
|
||||
op.flags_set_clear(set, clear);
|
||||
}
|
||||
}
|
||||
void Op::split_vars(const std::vector<TmpVar>& vars) {
|
||||
split_var_list(left, vars);
|
||||
split_var_list(right, vars);
|
||||
|
@ -294,7 +285,7 @@ void Op::show(std::ostream& os, const std::vector<TmpVar>& vars, std::string pfx
|
|||
if (noreturn()) {
|
||||
dis += "<noret> ";
|
||||
}
|
||||
if (!is_pure()) {
|
||||
if (impure()) {
|
||||
dis += "<impure> ";
|
||||
}
|
||||
switch (cl) {
|
||||
|
@ -467,12 +458,6 @@ void Op::show_block(std::ostream& os, const Op* block, const std::vector<TmpVar>
|
|||
os << pfx << "}";
|
||||
}
|
||||
|
||||
void CodeBlob::flags_set_clear(int set, int clear) {
|
||||
for (auto& op : ops) {
|
||||
op.flags_set_clear(set, clear);
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const CodeBlob& code) {
|
||||
code.print(os);
|
||||
return os;
|
||||
|
|
|
@ -360,10 +360,10 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) {
|
|||
case _Tuple:
|
||||
case _UnTuple: {
|
||||
// left = EXEC right;
|
||||
if (!next_var_info.count_used(left) && is_pure()) {
|
||||
if (!next_var_info.count_used(left) && !impure()) {
|
||||
// all variables in `left` are not needed
|
||||
if (edit) {
|
||||
disable();
|
||||
set_disabled();
|
||||
}
|
||||
return std_compute_used_vars(true);
|
||||
}
|
||||
|
@ -372,7 +372,7 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) {
|
|||
case _SetGlob: {
|
||||
// GLOB = right
|
||||
if (right.empty() && edit) {
|
||||
disable();
|
||||
set_disabled();
|
||||
}
|
||||
return std_compute_used_vars(right.empty());
|
||||
}
|
||||
|
@ -399,7 +399,7 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) {
|
|||
}
|
||||
if (!cnt && edit) {
|
||||
// all variables in `left` are not needed
|
||||
disable();
|
||||
set_disabled();
|
||||
}
|
||||
return set_var_info(std::move(new_var_info));
|
||||
}
|
||||
|
@ -860,15 +860,45 @@ VarDescrList Op::fwd_analyze(VarDescrList values) {
|
|||
}
|
||||
}
|
||||
|
||||
bool Op::set_noreturn(bool nr) {
|
||||
if (nr) {
|
||||
void Op::set_disabled(bool flag) {
|
||||
if (flag) {
|
||||
flags |= _Disabled;
|
||||
} else {
|
||||
flags &= ~_Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Op::set_noreturn(bool flag) {
|
||||
if (flag) {
|
||||
flags |= _NoReturn;
|
||||
} else {
|
||||
flags &= ~_NoReturn;
|
||||
}
|
||||
return nr;
|
||||
return flag;
|
||||
}
|
||||
|
||||
void Op::set_impure(const CodeBlob &code) {
|
||||
// todo calling this function with `code` is a bad design (flags are assigned after Op is constructed)
|
||||
// later it's better to check this somewhere in code.emplace_back()
|
||||
if (code.flags & CodeBlob::_ForbidImpure) {
|
||||
throw ParseError(where, "An impure operation in a pure function");
|
||||
}
|
||||
flags |= _Impure;
|
||||
}
|
||||
|
||||
void Op::set_impure(const CodeBlob &code, bool flag) {
|
||||
if (flag) {
|
||||
if (code.flags & CodeBlob::_ForbidImpure) {
|
||||
throw ParseError(where, "An impure operation in a pure function");
|
||||
}
|
||||
flags |= _Impure;
|
||||
} else {
|
||||
flags &= ~_Impure;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Op::mark_noreturn() {
|
||||
switch (cl) {
|
||||
case _Nop:
|
||||
|
@ -888,13 +918,14 @@ bool Op::mark_noreturn() {
|
|||
case _Call:
|
||||
return set_noreturn(next->mark_noreturn());
|
||||
case _Return:
|
||||
return set_noreturn(true);
|
||||
return set_noreturn();
|
||||
case _If:
|
||||
case _TryCatch:
|
||||
// note, that & | (not && ||) here and below is mandatory to invoke both left and right calls
|
||||
return set_noreturn((static_cast<int>(block0->mark_noreturn()) & static_cast<int>(block1 && block1->mark_noreturn())) | static_cast<int>(next->mark_noreturn()));
|
||||
case _Again:
|
||||
block0->mark_noreturn();
|
||||
return set_noreturn(true);
|
||||
return set_noreturn();
|
||||
case _Until:
|
||||
return set_noreturn(static_cast<int>(block0->mark_noreturn()) | static_cast<int>(next->mark_noreturn()));
|
||||
case _While:
|
||||
|
|
|
@ -317,6 +317,9 @@ void AsmOpList::show_var_ext(std::ostream& os, std::pair<var_idx_t, const_idx_t>
|
|||
os << '_' << i;
|
||||
} else {
|
||||
var_names_->at(i).show(os, 2);
|
||||
// if (!var_names_->at(i).v_type->is_int()) {
|
||||
// os << '<'; var_names_->at(i).v_type->print(os); os << '>';
|
||||
// }
|
||||
}
|
||||
if ((unsigned)j < constants_.size() && constants_[j].not_null()) {
|
||||
os << '=' << constants_[j];
|
||||
|
|
|
@ -26,10 +26,10 @@ using namespace std::literals::string_literals;
|
|||
*/
|
||||
|
||||
int glob_func_cnt, undef_func_cnt, glob_var_cnt, const_cnt;
|
||||
std::vector<SymDef*> glob_func, glob_vars;
|
||||
std::vector<SymDef*> glob_func, glob_vars, glob_get_methods;
|
||||
std::set<std::string> prohibited_var_names;
|
||||
|
||||
SymDef* predefine_builtin_func(std::string name, TypeExpr* func_type) {
|
||||
SymDef* define_builtin_func_impl(const std::string& name, SymValAsmFunc* func_val) {
|
||||
if (name.back() == '_') {
|
||||
prohibited_var_names.insert(name);
|
||||
}
|
||||
|
@ -42,30 +42,40 @@ SymDef* predefine_builtin_func(std::string name, TypeExpr* func_type) {
|
|||
std::cerr << "fatal: global function `" << name << "` already defined" << std::endl;
|
||||
std::exit(1);
|
||||
}
|
||||
func_val->flags |= SymValFunc::flagBuiltinFunction;
|
||||
def->value = func_val;
|
||||
#ifdef TOLK_DEBUG
|
||||
dynamic_cast<SymValAsmFunc*>(def->value)->name = name;
|
||||
#endif
|
||||
return def;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
SymDef* define_builtin_func(std::string name, TypeExpr* func_type, const T& func, bool impure = false) {
|
||||
SymDef* def = predefine_builtin_func(name, func_type);
|
||||
def->value = new SymValAsmFunc{func_type, func, impure};
|
||||
return def;
|
||||
SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const simple_compile_func_t& func, bool impure = false) {
|
||||
return define_builtin_func_impl(name, new SymValAsmFunc{func_type, func, !impure});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
SymDef* define_builtin_func(std::string name, TypeExpr* func_type, const T& func, std::initializer_list<int> arg_order,
|
||||
SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const compile_func_t& func, bool impure = false) {
|
||||
return define_builtin_func_impl(name, new SymValAsmFunc{func_type, func, !impure});
|
||||
}
|
||||
|
||||
SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const AsmOp& macro, bool impure = false) {
|
||||
return define_builtin_func_impl(name, new SymValAsmFunc{func_type, make_simple_compile(macro), !impure});
|
||||
}
|
||||
|
||||
SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const simple_compile_func_t& func, std::initializer_list<int> arg_order,
|
||||
std::initializer_list<int> ret_order = {}, bool impure = false) {
|
||||
SymDef* def = predefine_builtin_func(name, func_type);
|
||||
def->value = new SymValAsmFunc{func_type, func, arg_order, ret_order, impure};
|
||||
return def;
|
||||
return define_builtin_func_impl(name, new SymValAsmFunc{func_type, func, arg_order, ret_order, !impure});
|
||||
}
|
||||
|
||||
SymDef* define_builtin_func(std::string name, TypeExpr* func_type, const AsmOp& macro,
|
||||
SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const compile_func_t& func, std::initializer_list<int> arg_order,
|
||||
std::initializer_list<int> ret_order = {}, bool impure = false) {
|
||||
return define_builtin_func_impl(name, new SymValAsmFunc{func_type, func, arg_order, ret_order, !impure});
|
||||
}
|
||||
|
||||
SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const AsmOp& macro,
|
||||
std::initializer_list<int> arg_order, std::initializer_list<int> ret_order = {},
|
||||
bool impure = false) {
|
||||
SymDef* def = predefine_builtin_func(name, func_type);
|
||||
def->value = new SymValAsmFunc{func_type, make_simple_compile(macro), arg_order, ret_order, impure};
|
||||
return def;
|
||||
return define_builtin_func_impl(name, new SymValAsmFunc{func_type, make_simple_compile(macro), arg_order, ret_order, !impure});
|
||||
}
|
||||
|
||||
SymDef* force_autoapply(SymDef* def) {
|
||||
|
@ -262,7 +272,7 @@ int emulate_lshift(int a, int b) {
|
|||
}
|
||||
int t = ((b & VarDescr::_NonZero) ? VarDescr::_Even : 0);
|
||||
t |= b & VarDescr::_Finite;
|
||||
return emulate_mul(a, VarDescr::_Int | VarDescr::_Pos | VarDescr::_NonZero | VarDescr::_Even | t);
|
||||
return emulate_mul(a, VarDescr::_Int | VarDescr::_Pos | VarDescr::_NonZero | t);
|
||||
}
|
||||
|
||||
int emulate_div(int a, int b) {
|
||||
|
@ -308,7 +318,7 @@ int emulate_rshift(int a, int b) {
|
|||
}
|
||||
int t = ((b & VarDescr::_NonZero) ? VarDescr::_Even : 0);
|
||||
t |= b & VarDescr::_Finite;
|
||||
return emulate_div(a, VarDescr::_Int | VarDescr::_Pos | VarDescr::_NonZero | VarDescr::_Even | t);
|
||||
return emulate_div(a, VarDescr::_Int | VarDescr::_Pos | VarDescr::_NonZero | t);
|
||||
}
|
||||
|
||||
int emulate_mod(int a, int b, int round_mode = -1) {
|
||||
|
@ -1128,9 +1138,9 @@ void define_builtins() {
|
|||
auto Int3 = TypeExpr::new_tensor({Int, Int, Int});
|
||||
auto TupleInt = TypeExpr::new_tensor({Tuple, Int});
|
||||
auto SliceInt = TypeExpr::new_tensor({Slice, Int});
|
||||
auto X = TypeExpr::new_var();
|
||||
auto Y = TypeExpr::new_var();
|
||||
auto Z = TypeExpr::new_var();
|
||||
auto X = TypeExpr::new_var(0);
|
||||
auto Y = TypeExpr::new_var(1);
|
||||
auto Z = TypeExpr::new_var(2);
|
||||
auto XY = TypeExpr::new_tensor({X, Y});
|
||||
auto arith_bin_op = TypeExpr::new_map(Int2, Int);
|
||||
auto arith_un_op = TypeExpr::new_map(Int, Int);
|
||||
|
|
|
@ -437,6 +437,7 @@ bool Op::generate_code_step(Stack& stack) {
|
|||
if (disabled()) {
|
||||
return true;
|
||||
}
|
||||
// fun_ref can be nullptr for Op::_CallInd (invoke a variable, not a function)
|
||||
SymValFunc* func = (fun_ref ? dynamic_cast<SymValFunc*>(fun_ref->value) : nullptr);
|
||||
auto arg_order = (func ? func->get_arg_order() : nullptr);
|
||||
auto ret_order = (func ? func->get_ret_order() : nullptr);
|
||||
|
@ -486,20 +487,18 @@ bool Op::generate_code_step(Stack& stack) {
|
|||
};
|
||||
if (cl == _CallInd) {
|
||||
exec_callxargs((int)right.size() - 1, (int)left.size());
|
||||
} else {
|
||||
auto func = dynamic_cast<const SymValAsmFunc*>(fun_ref->value);
|
||||
if (func) {
|
||||
} else if (auto asm_fv = dynamic_cast<const SymValAsmFunc*>(fun_ref->value)) {
|
||||
std::vector<VarDescr> res;
|
||||
res.reserve(left.size());
|
||||
for (var_idx_t i : left) {
|
||||
res.emplace_back(i);
|
||||
}
|
||||
func->compile(stack.o, res, args, where); // compile res := f (args)
|
||||
asm_fv->compile(stack.o, res, args, where); // compile res := f (args)
|
||||
} else {
|
||||
auto fv = dynamic_cast<const SymValCodeFunc*>(fun_ref->value);
|
||||
// todo can be fv == nullptr?
|
||||
std::string name = symbols.get_name(fun_ref->sym_idx);
|
||||
bool is_inline = (fv && (fv->flags & 3));
|
||||
if (is_inline) {
|
||||
if (fv && (fv->is_inline() || fv->is_inline_ref())) {
|
||||
stack.o << AsmOp::Custom(name + " INLINECALLDICT", (int)right.size(), (int)left.size());
|
||||
} else if (fv && fv->code && fv->code->require_callxargs) {
|
||||
stack.o << AsmOp::Custom(name + (" PREPAREDICT"), 0, 2);
|
||||
|
@ -508,7 +507,6 @@ bool Op::generate_code_step(Stack& stack) {
|
|||
stack.o << AsmOp::Custom(name + " CALLDICT", (int)right.size(), (int)left.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
stack.s.resize(k);
|
||||
for (int i = 0; i < (int)left.size(); i++) {
|
||||
int j = ret_order ? ret_order->at(i) : i;
|
||||
|
|
|
@ -35,7 +35,7 @@ Expr* Expr::copy() const {
|
|||
return res;
|
||||
}
|
||||
|
||||
Expr::Expr(int c, sym_idx_t name_idx, std::initializer_list<Expr*> _arglist) : cls(c), args(std::move(_arglist)) {
|
||||
Expr::Expr(ExprCls c, sym_idx_t name_idx, std::initializer_list<Expr*> _arglist) : cls(c), args(std::move(_arglist)) {
|
||||
sym = lookup_symbol(name_idx);
|
||||
if (!sym) {
|
||||
}
|
||||
|
@ -227,11 +227,11 @@ var_idx_t Expr::new_tmp(CodeBlob& code) const {
|
|||
void add_set_globs(CodeBlob& code, std::vector<std::pair<SymDef*, var_idx_t>>& globs, const SrcLocation& here) {
|
||||
for (const auto& p : globs) {
|
||||
auto& op = code.emplace_back(here, Op::_SetGlob, std::vector<var_idx_t>{}, std::vector<var_idx_t>{ p.second }, p.first);
|
||||
op.flags |= Op::_Impure;
|
||||
op.set_impure(code);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<var_idx_t> Expr::pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rhs, const SrcLocation& here) {
|
||||
std::vector<var_idx_t> pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rhs, const SrcLocation& here) {
|
||||
while (lhs->is_type_apply()) {
|
||||
lhs = lhs->args.at(0);
|
||||
}
|
||||
|
@ -247,7 +247,7 @@ std::vector<var_idx_t> Expr::pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rh
|
|||
auto unpacked_type = rhs->e_type->args.at(0);
|
||||
std::vector<var_idx_t> tmp{code.create_tmp_var(unpacked_type, &rhs->here)};
|
||||
code.emplace_back(lhs->here, Op::_UnTuple, tmp, std::move(right));
|
||||
auto tvar = new Expr{_Var};
|
||||
auto tvar = new Expr{Expr::_Var};
|
||||
tvar->set_val(tmp[0]);
|
||||
tvar->set_location(rhs->here);
|
||||
tvar->e_type = unpacked_type;
|
||||
|
@ -265,45 +265,37 @@ std::vector<var_idx_t> Expr::pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rh
|
|||
return right;
|
||||
}
|
||||
|
||||
std::vector<var_idx_t> pre_compile_tensor(const std::vector<Expr *> args, CodeBlob &code,
|
||||
std::vector<std::pair<SymDef*, var_idx_t>> *lval_globs,
|
||||
std::vector<int> arg_order) {
|
||||
if (arg_order.empty()) {
|
||||
arg_order.resize(args.size());
|
||||
std::iota(arg_order.begin(), arg_order.end(), 0);
|
||||
std::vector<var_idx_t> pre_compile_tensor(const std::vector<Expr *>& args, CodeBlob &code,
|
||||
std::vector<std::pair<SymDef*, var_idx_t>> *lval_globs) {
|
||||
const size_t n = args.size();
|
||||
if (n == 0) { // just `()`
|
||||
return {};
|
||||
}
|
||||
tolk_assert(args.size() == arg_order.size());
|
||||
std::vector<std::vector<var_idx_t>> res_lists(args.size());
|
||||
if (n == 1) { // just `(x)`: even if x is modified (e.g. `f(x=x+2)`), there are no next arguments
|
||||
return args[0]->pre_compile(code, lval_globs);
|
||||
}
|
||||
std::vector<std::vector<var_idx_t>> res_lists(n);
|
||||
|
||||
struct ModifiedVar {
|
||||
size_t i, j;
|
||||
Op* op;
|
||||
std::unique_ptr<Op>* cur_ops; // `LET tmp = v_ij` will be inserted before this
|
||||
};
|
||||
auto modified_vars = std::make_shared<std::vector<ModifiedVar>>();
|
||||
for (size_t i : arg_order) {
|
||||
std::vector<ModifiedVar> modified_vars;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
res_lists[i] = args[i]->pre_compile(code, lval_globs);
|
||||
for (size_t j = 0; j < res_lists[i].size(); ++j) {
|
||||
TmpVar& var = code.vars.at(res_lists[i][j]);
|
||||
if (code.flags & CodeBlob::_AllowPostModification) {
|
||||
if (!lval_globs && (var.cls & TmpVar::_Named)) {
|
||||
Op *op = &code.emplace_back(nullptr, Op::_Let, std::vector<var_idx_t>(), std::vector<var_idx_t>());
|
||||
op->flags |= Op::_Disabled;
|
||||
var.on_modification.push_back([modified_vars, i, j, op, done = false](const SrcLocation &here) mutable {
|
||||
var.on_modification.push_back([&modified_vars, i, j, cur_ops = code.cur_ops, done = false](const SrcLocation &here) mutable {
|
||||
if (!done) {
|
||||
done = true;
|
||||
modified_vars->push_back({i, j, op});
|
||||
modified_vars.push_back({i, j, cur_ops});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var.on_modification.push_back([](const SrcLocation &) {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
var.on_modification.push_back([name = var.to_string()](const SrcLocation &here) {
|
||||
throw ParseError{here, PSTRING() << "Modifying local variable " << name
|
||||
<< " after using it in the same expression"};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& list : res_lists) {
|
||||
|
@ -312,13 +304,16 @@ std::vector<var_idx_t> pre_compile_tensor(const std::vector<Expr *> args, CodeBl
|
|||
code.vars.at(v).on_modification.pop_back();
|
||||
}
|
||||
}
|
||||
for (const ModifiedVar &m : *modified_vars) {
|
||||
var_idx_t& v = res_lists[m.i][m.j];
|
||||
var_idx_t v2 = code.create_tmp_var(code.vars[v].v_type, code.vars[v].where.get());
|
||||
m.op->left = {v2};
|
||||
m.op->right = {v};
|
||||
m.op->flags &= ~Op::_Disabled;
|
||||
v = v2;
|
||||
for (size_t idx = modified_vars.size(); idx--; ) {
|
||||
const ModifiedVar &m = modified_vars[idx];
|
||||
var_idx_t orig_v = res_lists[m.i][m.j];
|
||||
var_idx_t tmp_v = code.create_tmp_var(code.vars[orig_v].v_type, code.vars[orig_v].where.get());
|
||||
std::unique_ptr<Op> op = std::make_unique<Op>(*code.vars[orig_v].where, Op::_Let);
|
||||
op->left = {tmp_v};
|
||||
op->right = {orig_v};
|
||||
op->next = std::move((*m.cur_ops));
|
||||
*m.cur_ops = std::move(op);
|
||||
res_lists[m.i][m.j] = tmp_v;
|
||||
}
|
||||
std::vector<var_idx_t> res;
|
||||
for (const auto& list : res_lists) {
|
||||
|
@ -334,22 +329,33 @@ std::vector<var_idx_t> Expr::pre_compile(CodeBlob& code, std::vector<std::pair<S
|
|||
}
|
||||
switch (cls) {
|
||||
case _Tensor: {
|
||||
return pre_compile_tensor(args, code, lval_globs, {});
|
||||
return pre_compile_tensor(args, code, lval_globs);
|
||||
}
|
||||
case _Apply: {
|
||||
tolk_assert(sym);
|
||||
auto func = dynamic_cast<SymValFunc*>(sym->value);
|
||||
std::vector<var_idx_t> res;
|
||||
if (func && func->arg_order.size() == args.size() && !(code.flags & CodeBlob::_ComputeAsmLtr)) {
|
||||
//std::cerr << "!!! reordering " << args.size() << " arguments of " << sym->name() << std::endl;
|
||||
res = pre_compile_tensor(args, code, lval_globs, func->arg_order);
|
||||
SymDef* applied_sym = sym;
|
||||
auto func = dynamic_cast<SymValFunc*>(applied_sym->value);
|
||||
// replace `beginCell()` with `begin_cell()`
|
||||
if (func && func->is_just_wrapper_for_another_f()) {
|
||||
// body is { Op::_Import; Op::_Call; Op::_Return; }
|
||||
const std::unique_ptr<Op>& op_call = dynamic_cast<SymValCodeFunc*>(func)->code->ops->next;
|
||||
applied_sym = op_call->fun_ref;
|
||||
// a function may call anotherF with shuffled arguments: f(x,y) { return anotherF(y,x) }
|
||||
// then op_call looks like (_1,_0), so use op_call->right for correct positions in Op::_Call below
|
||||
// it's correct, since every argument has width 1
|
||||
std::vector<var_idx_t> res_inner = pre_compile_tensor(args, code, lval_globs);
|
||||
res.reserve(res_inner.size());
|
||||
for (var_idx_t right_idx : op_call->right) {
|
||||
res.emplace_back(res_inner[right_idx]);
|
||||
}
|
||||
} else {
|
||||
res = pre_compile_tensor(args, code, lval_globs, {});
|
||||
res = pre_compile_tensor(args, code, lval_globs);
|
||||
}
|
||||
auto rvect = new_tmp_vect(code);
|
||||
auto& op = code.emplace_back(here, Op::_Call, rvect, std::move(res), sym);
|
||||
auto& op = code.emplace_back(here, Op::_Call, rvect, res, applied_sym);
|
||||
if (flags & _IsImpure) {
|
||||
op.flags |= Op::_Impure;
|
||||
op.set_impure(code);
|
||||
}
|
||||
return rvect;
|
||||
}
|
||||
|
@ -362,12 +368,12 @@ std::vector<var_idx_t> Expr::pre_compile(CodeBlob& code, std::vector<std::pair<S
|
|||
}
|
||||
return {val};
|
||||
case _VarApply:
|
||||
if (args[0]->cls == _Glob) {
|
||||
if (args[0]->cls == _GlobFunc) {
|
||||
auto res = args[1]->pre_compile(code);
|
||||
auto rvect = new_tmp_vect(code);
|
||||
auto& op = code.emplace_back(here, Op::_Call, rvect, std::move(res), args[0]->sym);
|
||||
if (args[0]->flags & _IsImpure) {
|
||||
op.flags |= Op::_Impure;
|
||||
op.set_impure(code);
|
||||
}
|
||||
return rvect;
|
||||
} else {
|
||||
|
@ -386,8 +392,14 @@ std::vector<var_idx_t> Expr::pre_compile(CodeBlob& code, std::vector<std::pair<S
|
|||
code.emplace_back(here, Op::_IntConst, rvect, intval);
|
||||
return rvect;
|
||||
}
|
||||
case _Glob:
|
||||
case _GlobFunc:
|
||||
case _GlobVar: {
|
||||
if (auto fun_ref = dynamic_cast<SymValFunc*>(sym->value)) {
|
||||
fun_ref->flags |= SymValFunc::flagUsedAsNonCall;
|
||||
if (!fun_ref->arg_order.empty() || !fun_ref->ret_order.empty()) {
|
||||
throw ParseError(here, "Saving " + sym->name() + " into a variable will most likely lead to invalid usage, since it changes the order of variables on the stack");
|
||||
}
|
||||
}
|
||||
auto rvect = new_tmp_vect(code);
|
||||
if (lval_globs) {
|
||||
lval_globs->push_back({ sym, rvect[0] });
|
||||
|
|
|
@ -109,10 +109,13 @@ void define_keywords() {
|
|||
.add_keyword("global", Keyword::_Global)
|
||||
.add_keyword("asm", Keyword::_Asm)
|
||||
.add_keyword("impure", Keyword::_Impure)
|
||||
.add_keyword("pure", Keyword::_Pure)
|
||||
.add_keyword("inline", Keyword::_Inline)
|
||||
.add_keyword("inline_ref", Keyword::_InlineRef)
|
||||
.add_keyword("builtin", Keyword::_Builtin)
|
||||
.add_keyword("auto_apply", Keyword::_AutoApply)
|
||||
.add_keyword("method_id", Keyword::_MethodId)
|
||||
.add_keyword("get", Keyword::_Get)
|
||||
.add_keyword("operator", Keyword::_Operator)
|
||||
.add_keyword("infix", Keyword::_Infix)
|
||||
.add_keyword("infixl", Keyword::_Infixl)
|
||||
|
|
|
@ -122,8 +122,7 @@ int Lexem::set(std::string _str, const SrcLocation& _loc, int _tp, int _val) {
|
|||
return classify();
|
||||
}
|
||||
|
||||
Lexer::Lexer(SourceReader& _src, bool init, std::string active_chars, std::string eol_cmts, std::string open_cmts,
|
||||
std::string close_cmts, std::string quote_chars, std::string multiline_quote)
|
||||
Lexer::Lexer(SourceReader& _src, std::string active_chars, std::string quote_chars, std::string multiline_quote)
|
||||
: src(_src), eof(false), lexem("", src.here(), Lexem::Undefined), peek_lexem("", {}, Lexem::Undefined),
|
||||
multiline_quote(std::move(multiline_quote)) {
|
||||
std::memset(char_class, 0, sizeof(char_class));
|
||||
|
@ -137,17 +136,27 @@ Lexer::Lexer(SourceReader& _src, bool init, std::string active_chars, std::strin
|
|||
char_class[(unsigned)c] |= activity;
|
||||
}
|
||||
}
|
||||
set_spec(eol_cmt, eol_cmts);
|
||||
set_spec(cmt_op, open_cmts);
|
||||
set_spec(cmt_cl, close_cmts);
|
||||
for (int c : quote_chars) {
|
||||
if (c > ' ' && c <= 0x7f) {
|
||||
char_class[(unsigned)c] |= cc::quote_char;
|
||||
}
|
||||
}
|
||||
if (init) {
|
||||
}
|
||||
|
||||
void Lexer::set_comment_tokens(const std::string &eol_cmts, const std::string &open_cmts, const std::string &close_cmts) {
|
||||
set_spec(eol_cmt, eol_cmts);
|
||||
set_spec(cmt_op, open_cmts);
|
||||
set_spec(cmt_cl, close_cmts);
|
||||
}
|
||||
|
||||
void Lexer::set_comment2_tokens(const std::string &eol_cmts2, const std::string &open_cmts2, const std::string &close_cmts2) {
|
||||
set_spec(eol_cmt2, eol_cmts2);
|
||||
set_spec(cmt_op2, open_cmts2);
|
||||
set_spec(cmt_cl2, close_cmts2);
|
||||
}
|
||||
|
||||
void Lexer::start_parsing() {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
void Lexer::set_spec(std::array<int, 3>& arr, std::string setup) {
|
||||
|
@ -202,31 +211,41 @@ const Lexem& Lexer::next() {
|
|||
return lexem.clear(src.here(), Lexem::Eof);
|
||||
}
|
||||
long long comm = 1;
|
||||
// the code below is very complicated, because it tried to support one-symbol start/end and nesting
|
||||
// in Tolk, we decided to stop supporting nesting (it was never used in practice and almost impossible for js highlighters)
|
||||
// later on I'll simplify this code (more precisely, rewrite lexer from scratch)
|
||||
while (!src.seek_eof()) {
|
||||
int cc = src.cur_char(), nc = src.next_char();
|
||||
if (cc == eol_cmt[0] || (cc == eol_cmt[1] && nc == eol_cmt[2])) {
|
||||
// note, that in practice, [0]-th element is -256, condition for [0]-th is always false
|
||||
// todo rewrite this all in the future
|
||||
if (cc == eol_cmt[0] || (cc == eol_cmt[1] && nc == eol_cmt[2]) || cc == eol_cmt2[0] || (cc == eol_cmt2[1] && nc == eol_cmt2[2])) {
|
||||
if (comm == 1) { // just "//" — skip a whole line
|
||||
src.load_line();
|
||||
} else if (cc == cmt_op[1] && nc == cmt_op[2]) {
|
||||
} else { // if "//" is nested into "/*", continue reading, since "*/" may be met
|
||||
src.advance(1);
|
||||
}
|
||||
} else if (cc == cmt_op[1] && nc == cmt_op[2] || cc == cmt_op2[1] && nc == cmt_op2[2]) {
|
||||
src.advance(2);
|
||||
comm = comm * 2 + 1;
|
||||
} else if (cc == cmt_op[0]) {
|
||||
} else if (cc == cmt_op[0] || cc == cmt_op2[0]) { // always false
|
||||
src.advance(1);
|
||||
comm *= 2;
|
||||
} else if (comm == 1) {
|
||||
break;
|
||||
} else if (cc == cmt_cl[1] && nc == cmt_cl[2]) {
|
||||
if (!(comm & 1)) {
|
||||
break; // means that we are not inside a comment
|
||||
} else if (cc == cmt_cl[1] && nc == cmt_cl[2] || cc == cmt_cl2[1] && nc == cmt_cl2[2]) {
|
||||
if (!(comm & 1)) { // always false
|
||||
src.error(std::string{"a `"} + (char)cmt_op[0] + "` comment closed by `" + (char)cmt_cl[1] + (char)cmt_cl[2] +
|
||||
"`");
|
||||
}
|
||||
comm >>= 1;
|
||||
// note that {- may be closed with */, but assume it's ok (we'll get rid of {- in the future)
|
||||
comm = 1;
|
||||
src.advance(2);
|
||||
} else if (cc == cmt_cl[0]) {
|
||||
} else if (cc == cmt_cl[0] || cc == cmt_cl2[0]) { // always false
|
||||
if (!(comm & 1)) {
|
||||
src.error(std::string{"a `"} + (char)cmt_op[1] + (char)cmt_op[2] + "` comment closed by `" + (char)cmt_cl[0] +
|
||||
"`");
|
||||
}
|
||||
comm >>= 1;
|
||||
comm = 1;
|
||||
src.advance(1);
|
||||
} else {
|
||||
src.advance(1);
|
||||
|
@ -238,11 +257,7 @@ const Lexem& Lexer::next() {
|
|||
if (src.seek_eof()) {
|
||||
eof = true;
|
||||
if (comm > 1) {
|
||||
if (comm & 1) {
|
||||
src.error(std::string{"`"} + (char)cmt_op[1] + (char)cmt_op[2] + "` comment extends past end of file");
|
||||
} else {
|
||||
src.error(std::string{"`"} + (char)cmt_op[0] + "` comment extends past end of file");
|
||||
}
|
||||
src.error("comment extends past end of file");
|
||||
}
|
||||
return lexem.clear(src.here(), Lexem::Eof);
|
||||
}
|
||||
|
|
13
tolk/lexer.h
13
tolk/lexer.h
|
@ -66,7 +66,8 @@ class Lexer {
|
|||
bool eof;
|
||||
Lexem lexem, peek_lexem;
|
||||
unsigned char char_class[128];
|
||||
std::array<int, 3> eol_cmt, cmt_op, cmt_cl;
|
||||
std::array<int, 3> eol_cmt, cmt_op, cmt_cl; // for ;; {- -}
|
||||
std::array<int, 3> eol_cmt2, cmt_op2, cmt_cl2; // for // /* */
|
||||
std::string multiline_quote;
|
||||
enum cc { left_active = 2, right_active = 1, active = 3, allow_repeat = 4, quote_char = 8 };
|
||||
|
||||
|
@ -74,9 +75,13 @@ class Lexer {
|
|||
bool eof_found() const {
|
||||
return eof;
|
||||
}
|
||||
Lexer(SourceReader& _src, bool init = false, std::string active_chars = ";,() ~.", std::string eol_cmts = ";;",
|
||||
std::string open_cmts = "{-", std::string close_cmts = "-}", std::string quote_chars = "\"",
|
||||
std::string multiline_quote = "\"\"\"");
|
||||
explicit Lexer(SourceReader& _src, std::string active_chars = ";,() ~.",
|
||||
std::string quote_chars = "\"", std::string multiline_quote = "\"\"\"");
|
||||
|
||||
void set_comment_tokens(const std::string &eol_cmts, const std::string &open_cmts, const std::string &close_cmts);
|
||||
void set_comment2_tokens(const std::string &eol_cmts2, const std::string &open_cmts2, const std::string &close_cmts2);
|
||||
void start_parsing();
|
||||
|
||||
const Lexem& next();
|
||||
const Lexem& cur() const {
|
||||
return lexem;
|
||||
|
|
|
@ -48,6 +48,109 @@ inline bool is_special_ident(sym_idx_t idx) {
|
|||
return symbols.get_subclass(idx) != IdSc::undef;
|
||||
}
|
||||
|
||||
// given Expr::_Apply (a function call / a variable call), determine whether it's <, or >, or similar
|
||||
// (an expression `1 < 2` is expressed as `_<_(1,2)`, see builtins.cpp)
|
||||
static bool is_comparison_binary_op(const Expr* e_apply) {
|
||||
const std::string& name = e_apply->sym->name();
|
||||
const size_t len = name.size();
|
||||
if (len < 3 || len > 5 || name[0] != '_' || name[len-1] != '_') {
|
||||
return false; // not "_<_" and similar
|
||||
}
|
||||
|
||||
char c1 = name[1];
|
||||
char c2 = name[2];
|
||||
// < > <= != == >= <=>
|
||||
return (len == 3 && (c1 == '<' || c1 == '>')) ||
|
||||
(len == 4 && (c1 == '<' || c1 == '>' || c1 == '!' || c1 == '=') && c2 == '=') ||
|
||||
(len == 5 && (c1 == '<' && c2 == '=' && name[3] == '>'));
|
||||
}
|
||||
|
||||
// same as above, but to detect bitwise operators: & | ^
|
||||
// (in Tolk, they are used as logical ones due to absence of a boolean type and && || operators)
|
||||
static bool is_bitwise_binary_op(const Expr* e_apply) {
|
||||
const std::string& name = e_apply->sym->name();
|
||||
const size_t len = name.size();
|
||||
if (len != 3 || name[0] != '_' || name[len-1] != '_') {
|
||||
return false;
|
||||
}
|
||||
|
||||
char c1 = name[1];
|
||||
return c1 == '&' || c1 == '|' || c1 == '^';
|
||||
}
|
||||
|
||||
// same as above, but to detect addition/subtraction
|
||||
static bool is_add_or_sub_binary_op(const Expr* e_apply) {
|
||||
const std::string& name = e_apply->sym->name();
|
||||
const size_t len = name.size();
|
||||
if (len != 3 || name[0] != '_' || name[len-1] != '_') {
|
||||
return false;
|
||||
}
|
||||
|
||||
char c1 = name[1];
|
||||
return c1 == '+' || c1 == '-';
|
||||
}
|
||||
|
||||
static inline std::string get_builtin_operator_name(sym_idx_t sym_builtin) {
|
||||
std::string underscored = symbols.get_name(sym_builtin);
|
||||
return underscored.substr(1, underscored.size() - 2);
|
||||
}
|
||||
|
||||
// fire an error for a case "flags & 0xFF != 0" (equivalent to "flags & 1", probably unexpected)
|
||||
// it would better be a warning, but we decided to make it a strict error
|
||||
[[gnu::cold]] static void fire_error_lower_precedence(const SrcLocation& loc, sym_idx_t op_lower, sym_idx_t op_higher) {
|
||||
std::string name_lower = get_builtin_operator_name(op_lower);
|
||||
std::string name_higher = get_builtin_operator_name(op_higher);
|
||||
throw ParseError(loc, name_lower + " has lower precedence than " + name_higher +
|
||||
", probably this code won't work as you expected. "
|
||||
"Use parenthesis: either (... " + name_lower + " ...) to evaluate it first, or (... " + name_higher + " ...) to suppress this error.");
|
||||
}
|
||||
|
||||
// fire an error for a case "arg1 & arg2 | arg3"
|
||||
[[gnu::cold]] static void fire_error_mix_bitwise_and_or(const SrcLocation& loc, sym_idx_t op1, sym_idx_t op2) {
|
||||
std::string name1 = get_builtin_operator_name(op1);
|
||||
std::string name2 = get_builtin_operator_name(op2);
|
||||
throw ParseError(loc, "mixing " + name1 + " with " + name2 + " without parenthesis"
|
||||
", probably this code won't work as you expected. "
|
||||
"Use parenthesis to emphasize operator precedence.");
|
||||
}
|
||||
|
||||
// diagnose when bitwise operators are used in a probably wrong way due to tricky precedence
|
||||
// example: "flags & 0xFF != 0" is equivalent to "flags & 1", most likely it's unexpected
|
||||
// the only way to suppress this error for the programmer is to use parenthesis
|
||||
static void diagnose_bitwise_precedence(const SrcLocation& loc, sym_idx_t bitwise_sym, const Expr* lhs, const Expr* rhs) {
|
||||
// handle "0 != flags & 0xFF" (lhs = "0 != flags")
|
||||
if (!lhs->is_inside_parenthesis() &&
|
||||
lhs->cls == Expr::_Apply && lhs->e_type->is_int() && // fast false if 100% not
|
||||
is_comparison_binary_op(lhs)) {
|
||||
fire_error_lower_precedence(loc, bitwise_sym, lhs->sym->sym_idx);
|
||||
// there is a tiny bug: "flags & _!=_(0xFF,0)" will also suggest to wrap rhs into parenthesis
|
||||
}
|
||||
|
||||
// handle "flags & 0xFF != 0" (rhs = "0xFF != 0")
|
||||
if (!rhs->is_inside_parenthesis() &&
|
||||
rhs->cls == Expr::_Apply && rhs->e_type->is_int() &&
|
||||
is_comparison_binary_op(rhs)) {
|
||||
fire_error_lower_precedence(loc, bitwise_sym, rhs->sym->sym_idx);
|
||||
}
|
||||
|
||||
// handle "arg1 & arg2 | arg3" (lhs = "arg1 & arg2")
|
||||
if (!lhs->is_inside_parenthesis() &&
|
||||
lhs->cls == Expr::_Apply && lhs->e_type->is_int() &&
|
||||
is_bitwise_binary_op(lhs) &&
|
||||
lhs->sym->sym_idx != bitwise_sym) {
|
||||
fire_error_mix_bitwise_and_or(loc, lhs->sym->sym_idx, bitwise_sym);
|
||||
}
|
||||
}
|
||||
|
||||
// diagnose "a << 8 + 1" (equivalent to "a << 9", probably unexpected)
|
||||
static void diagnose_addition_in_bitshift(const SrcLocation& loc, sym_idx_t bitshift_sym, const Expr* rhs) {
|
||||
if (!rhs->is_inside_parenthesis() &&
|
||||
rhs->cls == Expr::_Apply && rhs->e_type->is_int() &&
|
||||
is_add_or_sub_binary_op(rhs)) {
|
||||
fire_error_lower_precedence(loc, bitshift_sym, rhs->sym->sym_idx);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* PARSE SOURCE
|
||||
|
@ -220,6 +323,9 @@ void parse_global_var_decl(Lexer& lex) {
|
|||
}
|
||||
} else {
|
||||
sym_def->value = new SymValGlobVar{glob_var_cnt++, var_type};
|
||||
#ifdef TOLK_DEBUG
|
||||
dynamic_cast<SymValGlobVar*>(sym_def->value)->name = lex.cur().str;
|
||||
#endif
|
||||
glob_vars.push_back(sym_def);
|
||||
}
|
||||
lex.next();
|
||||
|
@ -253,15 +359,9 @@ void parse_const_decl(Lexer& lex) {
|
|||
}
|
||||
lex.next();
|
||||
CodeBlob code;
|
||||
if (pragma_allow_post_modification.enabled()) {
|
||||
code.flags |= CodeBlob::_AllowPostModification;
|
||||
}
|
||||
if (pragma_compute_asm_ltr.enabled()) {
|
||||
code.flags |= CodeBlob::_ComputeAsmLtr;
|
||||
}
|
||||
// Handles processing and resolution of literals and consts
|
||||
auto x = parse_expr(lex, code, false); // also does lex.next() !
|
||||
if (x->flags != Expr::_IsRvalue) {
|
||||
if (!x->is_rvalue()) {
|
||||
lex.cur().error("expression is not strictly Rvalue");
|
||||
}
|
||||
if ((wanted_type == Expr::_Const) && (x->cls == Expr::_Apply))
|
||||
|
@ -274,7 +374,7 @@ void parse_const_decl(Lexer& lex) {
|
|||
new_value = new SymValConst{const_cnt++, x->intval};
|
||||
} else if (x->cls == Expr::_SliceConst) { // Slice constant (string)
|
||||
new_value = new SymValConst{const_cnt++, x->strval};
|
||||
} else if (x->cls == Expr::_Apply) {
|
||||
} else if (x->cls == Expr::_Apply) { // even "1 + 2" is Expr::_Apply (it applies `_+_`)
|
||||
code.emplace_back(loc, Op::_Import, std::vector<var_idx_t>());
|
||||
auto tmp_vars = x->pre_compile(code);
|
||||
code.emplace_back(loc, Op::_Return, std::move(tmp_vars));
|
||||
|
@ -372,27 +472,22 @@ void parse_global_var_decls(Lexer& lex) {
|
|||
lex.expect(';');
|
||||
}
|
||||
|
||||
SymValCodeFunc* make_new_glob_func(SymDef* func_sym, TypeExpr* func_type, bool impure = false) {
|
||||
SymValCodeFunc* res = new SymValCodeFunc{glob_func_cnt, func_type, impure};
|
||||
SymValCodeFunc* make_new_glob_func(SymDef* func_sym, TypeExpr* func_type, bool marked_as_pure) {
|
||||
SymValCodeFunc* res = new SymValCodeFunc{glob_func_cnt, func_type, marked_as_pure};
|
||||
#ifdef TOLK_DEBUG
|
||||
res->name = func_sym->name();
|
||||
#endif
|
||||
func_sym->value = res;
|
||||
glob_func.push_back(func_sym);
|
||||
glob_func_cnt++;
|
||||
return res;
|
||||
}
|
||||
|
||||
bool check_global_func(const Lexem& cur, sym_idx_t func_name = 0) {
|
||||
if (!func_name) {
|
||||
func_name = cur.val;
|
||||
}
|
||||
bool check_global_func(const Lexem& cur, sym_idx_t func_name) {
|
||||
SymDef* def = lookup_symbol(func_name);
|
||||
if (!def) {
|
||||
cur.loc.show_error(std::string{"undefined function `"} + symbols.get_name(func_name) +
|
||||
"`, defining a global function of unknown type");
|
||||
def = define_global_symbol(func_name, 0, cur.loc);
|
||||
tolk_assert(def && "cannot define global function");
|
||||
++undef_func_cnt;
|
||||
make_new_glob_func(def, TypeExpr::new_func()); // was: ... ::new_func()
|
||||
return true;
|
||||
cur.error("undefined symbol `" + symbols.get_name(func_name) + "`");
|
||||
return false;
|
||||
}
|
||||
SymVal* val = dynamic_cast<SymVal*>(def->value);
|
||||
if (!val) {
|
||||
|
@ -407,8 +502,8 @@ bool check_global_func(const Lexem& cur, sym_idx_t func_name = 0) {
|
|||
}
|
||||
|
||||
Expr* make_func_apply(Expr* fun, Expr* x) {
|
||||
Expr* res;
|
||||
if (fun->cls == Expr::_Glob) {
|
||||
Expr* res{nullptr};
|
||||
if (fun->cls == Expr::_GlobFunc) {
|
||||
if (x->cls == Expr::_Tensor) {
|
||||
res = new Expr{Expr::_Apply, fun->sym, x->args};
|
||||
} else {
|
||||
|
@ -445,6 +540,7 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) {
|
|||
}
|
||||
Expr* res = parse_expr(lex, code, nv);
|
||||
if (lex.tp() == ')') {
|
||||
res->flags |= Expr::_IsInsideParenthesis;
|
||||
lex.expect(clbr);
|
||||
return res;
|
||||
}
|
||||
|
@ -571,7 +667,7 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) {
|
|||
if (t == '_') {
|
||||
Expr* res = new Expr{Expr::_Hole, lex.cur().loc};
|
||||
res->val = -1;
|
||||
res->flags = (Expr::_IsLvalue | Expr::_IsHole | Expr::_IsNewVar);
|
||||
res->flags = Expr::_IsLvalue;
|
||||
res->e_type = TypeExpr::new_hole();
|
||||
lex.next();
|
||||
return res;
|
||||
|
@ -633,15 +729,16 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) {
|
|||
if (nv) {
|
||||
res->val = ~lex.cur().val;
|
||||
res->e_type = TypeExpr::new_hole();
|
||||
res->flags = Expr::_IsLvalue | Expr::_IsNewVar;
|
||||
res->flags = Expr::_IsLvalue;
|
||||
// std::cerr << "defined new variable " << lex.cur().str << " : " << res->e_type << std::endl;
|
||||
} else {
|
||||
if (!sym) {
|
||||
check_global_func(lex.cur());
|
||||
check_global_func(lex.cur(), lex.cur().val);
|
||||
sym = lookup_symbol(lex.cur().val);
|
||||
}
|
||||
res->sym = sym;
|
||||
SymVal* val = nullptr;
|
||||
bool impure = false;
|
||||
if (sym) {
|
||||
val = dynamic_cast<SymVal*>(sym->value);
|
||||
}
|
||||
|
@ -649,8 +746,9 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) {
|
|||
lex.cur().error_at("undefined identifier `", "`");
|
||||
} else if (val->type == SymVal::_Func) {
|
||||
res->e_type = val->get_type();
|
||||
res->cls = Expr::_Glob;
|
||||
res->cls = Expr::_GlobFunc;
|
||||
auto_apply = val->auto_apply;
|
||||
impure = !dynamic_cast<SymValFunc*>(val)->is_marked_as_pure();
|
||||
} else if (val->idx < 0) {
|
||||
lex.cur().error_at("accessing variable `", "` being defined");
|
||||
} else {
|
||||
|
@ -659,7 +757,7 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) {
|
|||
// std::cerr << "accessing variable " << lex.cur().str << " : " << res->e_type << std::endl;
|
||||
}
|
||||
// std::cerr << "accessing symbol " << lex.cur().str << " : " << res->e_type << (val->impure ? " (impure)" : " (pure)") << std::endl;
|
||||
res->flags = Expr::_IsLvalue | Expr::_IsRvalue | (val->impure ? Expr::_IsImpure : 0);
|
||||
res->flags = Expr::_IsLvalue | Expr::_IsRvalue | (impure ? Expr::_IsImpure : 0);
|
||||
}
|
||||
if (auto_apply) {
|
||||
int impure = res->flags & Expr::_IsImpure;
|
||||
|
@ -750,7 +848,7 @@ Expr* parse_expr80(Lexer& lex, CodeBlob& code, bool nv) {
|
|||
res = new Expr{Expr::_Apply, name, {obj, x}};
|
||||
}
|
||||
res->here = loc;
|
||||
res->flags = Expr::_IsRvalue | (val->impure ? Expr::_IsImpure : 0);
|
||||
res->flags = Expr::_IsRvalue | (val->is_marked_as_pure() ? 0 : Expr::_IsImpure);
|
||||
res->deduce_type(lex.cur());
|
||||
if (modify) {
|
||||
auto tmp = res;
|
||||
|
@ -784,11 +882,11 @@ Expr* parse_expr75(Lexer& lex, CodeBlob& code, bool nv) {
|
|||
}
|
||||
}
|
||||
|
||||
// parse E { (* | / | % | /% ) E }
|
||||
// parse E { (* | / | % | /% | ^/ | ~/ | ^% | ~% ) E }
|
||||
Expr* parse_expr30(Lexer& lex, CodeBlob& code, bool nv) {
|
||||
Expr* res = parse_expr75(lex, code, nv);
|
||||
while (lex.tp() == '*' || lex.tp() == '/' || lex.tp() == '%' || lex.tp() == _DivMod || lex.tp() == _DivC ||
|
||||
lex.tp() == _DivR || lex.tp() == _ModC || lex.tp() == _ModR || lex.tp() == '&') {
|
||||
lex.tp() == _DivR || lex.tp() == _ModC || lex.tp() == _ModR) {
|
||||
res->chk_rvalue(lex.cur());
|
||||
int t = lex.tp();
|
||||
sym_idx_t name = symbols.lookup_add(std::string{"_"} + lex.cur().str + "_");
|
||||
|
@ -806,7 +904,7 @@ Expr* parse_expr30(Lexer& lex, CodeBlob& code, bool nv) {
|
|||
return res;
|
||||
}
|
||||
|
||||
// parse [-] E { (+ | - | `|` | ^) E }
|
||||
// parse [-] E { (+ | -) E }
|
||||
Expr* parse_expr20(Lexer& lex, CodeBlob& code, bool nv) {
|
||||
Expr* res;
|
||||
int t = lex.tp();
|
||||
|
@ -825,7 +923,7 @@ Expr* parse_expr20(Lexer& lex, CodeBlob& code, bool nv) {
|
|||
} else {
|
||||
res = parse_expr30(lex, code, nv);
|
||||
}
|
||||
while (lex.tp() == '-' || lex.tp() == '+' || lex.tp() == '|' || lex.tp() == '^') {
|
||||
while (lex.tp() == '-' || lex.tp() == '+') {
|
||||
res->chk_rvalue(lex.cur());
|
||||
t = lex.tp();
|
||||
sym_idx_t name = symbols.lookup_add(std::string{"_"} + lex.cur().str + "_");
|
||||
|
@ -843,7 +941,7 @@ Expr* parse_expr20(Lexer& lex, CodeBlob& code, bool nv) {
|
|||
return res;
|
||||
}
|
||||
|
||||
// parse E { ( << | >> | >>~ | >>^ ) E }
|
||||
// parse E { ( << | >> | ~>> | ^>> ) E }
|
||||
Expr* parse_expr17(Lexer& lex, CodeBlob& code, bool nv) {
|
||||
Expr* res = parse_expr20(lex, code, nv);
|
||||
while (lex.tp() == _Lshift || lex.tp() == _Rshift || lex.tp() == _RshiftC || lex.tp() == _RshiftR) {
|
||||
|
@ -855,6 +953,7 @@ Expr* parse_expr17(Lexer& lex, CodeBlob& code, bool nv) {
|
|||
lex.next();
|
||||
auto x = parse_expr20(lex, code, false);
|
||||
x->chk_rvalue(lex.cur());
|
||||
diagnose_addition_in_bitshift(loc, name, x);
|
||||
res = new Expr{Expr::_Apply, name, {res, x}};
|
||||
res->here = loc;
|
||||
res->set_val(t);
|
||||
|
@ -886,9 +985,33 @@ Expr* parse_expr15(Lexer& lex, CodeBlob& code, bool nv) {
|
|||
return res;
|
||||
}
|
||||
|
||||
// parse E { ( & | `|` | ^ ) E }
|
||||
Expr* parse_expr14(Lexer& lex, CodeBlob& code, bool nv) {
|
||||
Expr* res = parse_expr15(lex, code, nv);
|
||||
while (lex.tp() == '&' || lex.tp() == '|' || lex.tp() == '^') {
|
||||
res->chk_rvalue(lex.cur());
|
||||
int t = lex.tp();
|
||||
sym_idx_t name = symbols.lookup_add(std::string{"_"} + lex.cur().str + "_");
|
||||
check_global_func(lex.cur(), name);
|
||||
SrcLocation loc{lex.cur().loc};
|
||||
lex.next();
|
||||
auto x = parse_expr15(lex, code, false);
|
||||
x->chk_rvalue(lex.cur());
|
||||
// diagnose tricky bitwise precedence, like "flags & 0xFF != 0" (& has lower precedence)
|
||||
diagnose_bitwise_precedence(loc, name, res, x);
|
||||
|
||||
res = new Expr{Expr::_Apply, name, {res, x}};
|
||||
res->here = loc;
|
||||
res->set_val(t);
|
||||
res->flags = Expr::_IsRvalue;
|
||||
res->deduce_type(lex.cur());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// parse E [ ? E : E ]
|
||||
Expr* parse_expr13(Lexer& lex, CodeBlob& code, bool nv) {
|
||||
Expr* res = parse_expr15(lex, code, nv);
|
||||
Expr* res = parse_expr14(lex, code, nv);
|
||||
if (lex.tp() == '?') {
|
||||
res->chk_rvalue(lex.cur());
|
||||
SrcLocation loc{lex.cur().loc};
|
||||
|
@ -1207,14 +1330,11 @@ blk_fl::val parse_stmt(Lexer& lex, CodeBlob& code) {
|
|||
}
|
||||
}
|
||||
|
||||
CodeBlob* parse_func_body(Lexer& lex, FormalArgList arg_list, TypeExpr* ret_type) {
|
||||
CodeBlob* parse_func_body(Lexer& lex, FormalArgList arg_list, TypeExpr* ret_type, bool marked_as_pure) {
|
||||
lex.expect('{');
|
||||
CodeBlob* blob = new CodeBlob{ret_type};
|
||||
if (pragma_allow_post_modification.enabled()) {
|
||||
blob->flags |= CodeBlob::_AllowPostModification;
|
||||
}
|
||||
if (pragma_compute_asm_ltr.enabled()) {
|
||||
blob->flags |= CodeBlob::_ComputeAsmLtr;
|
||||
if (marked_as_pure) {
|
||||
blob->flags |= CodeBlob::_ForbidImpure;
|
||||
}
|
||||
blob->import_params(std::move(arg_list));
|
||||
blk_fl::val res = blk_fl::init;
|
||||
|
@ -1235,7 +1355,7 @@ CodeBlob* parse_func_body(Lexer& lex, FormalArgList arg_list, TypeExpr* ret_type
|
|||
}
|
||||
|
||||
SymValAsmFunc* parse_asm_func_body(Lexer& lex, TypeExpr* func_type, const FormalArgList& arg_list, TypeExpr* ret_type,
|
||||
bool impure = false) {
|
||||
bool marked_as_pure) {
|
||||
auto loc = lex.cur().loc;
|
||||
lex.expect(_Asm);
|
||||
int cnt = (int)arg_list.size();
|
||||
|
@ -1339,14 +1459,14 @@ SymValAsmFunc* parse_asm_func_body(Lexer& lex, TypeExpr* func_type, const Formal
|
|||
for (const AsmOp& asm_op : asm_ops) {
|
||||
crc_s += asm_op.op;
|
||||
}
|
||||
crc_s.push_back(impure);
|
||||
crc_s.push_back(!marked_as_pure);
|
||||
for (const int& x : arg_order) {
|
||||
crc_s += std::string((const char*) (&x), (const char*) (&x + 1));
|
||||
}
|
||||
for (const int& x : ret_order) {
|
||||
crc_s += std::string((const char*) (&x), (const char*) (&x + 1));
|
||||
}
|
||||
auto res = new SymValAsmFunc{func_type, asm_ops, impure};
|
||||
auto res = new SymValAsmFunc{func_type, std::move(asm_ops), marked_as_pure};
|
||||
res->arg_order = std::move(arg_order);
|
||||
res->ret_order = std::move(ret_order);
|
||||
res->crc = td::crc64(crc_s);
|
||||
|
@ -1420,12 +1540,90 @@ TypeExpr* compute_type_closure(TypeExpr* expr, const std::vector<TypeExpr*>& typ
|
|||
return expr;
|
||||
}
|
||||
|
||||
// if a function looks like `T f(...args) { return anotherF(...args); }`,
|
||||
// set a bit to flags
|
||||
// then, all calls to `f(...)` will be effectively replaced with `anotherF(...)`
|
||||
void detect_if_function_just_wraps_another(SymValCodeFunc* v_current, const td::RefInt256 &method_id) {
|
||||
const std::string& function_name = v_current->code->name;
|
||||
|
||||
// in "AST" representation, the first is Op::_Import (input arguments, even if none)
|
||||
const auto& op_import = v_current->code->ops;
|
||||
tolk_assert(op_import && op_import->cl == Op::_Import);
|
||||
|
||||
// then Op::_Call (anotherF)
|
||||
const Op* op_call = op_import->next.get();
|
||||
if (!op_call || op_call->cl != Op::_Call)
|
||||
return;
|
||||
tolk_assert(op_call->left.size() == 1);
|
||||
|
||||
const auto& op_return = op_call->next;
|
||||
if (!op_return || op_return->cl != Op::_Return || op_return->left.size() != 1)
|
||||
return;
|
||||
|
||||
bool indices_expected = static_cast<int>(op_import->left.size()) == op_call->left[0] && op_call->left[0] == op_return->left[0];
|
||||
if (!indices_expected)
|
||||
return;
|
||||
|
||||
const SymDef* f_called = op_call->fun_ref;
|
||||
const SymValFunc* v_called = dynamic_cast<SymValFunc*>(f_called->value);
|
||||
if (!v_called)
|
||||
return;
|
||||
|
||||
// `return` must use all arguments, e.g. `return (_0,_2,_1)`, not `return (_0,_1,_1)`
|
||||
int args_used_mask = 0;
|
||||
for (var_idx_t arg_idx : op_call->right) {
|
||||
args_used_mask |= 1 << arg_idx;
|
||||
}
|
||||
if (args_used_mask != (1 << op_call->right.size()) - 1)
|
||||
return;
|
||||
|
||||
// detect getters (having method_id), they should not be treated as wrappers
|
||||
// v_current->method_id will be assigned later; todo refactor function parsing completely, it's weird
|
||||
// moreover, `recv_external()` and others are also exported, but FunC is unaware of method_id
|
||||
// (it's assigned by Fift later)
|
||||
// so, for now, just handle "special" function names, the same as in Asm.fif
|
||||
if (!method_id.is_null())
|
||||
return;
|
||||
if (function_name == "main" || function_name == "recv_internal" || function_name == "recv_external" ||
|
||||
function_name == "run_ticktock" || function_name == "split_prepare" || function_name == "split_install")
|
||||
return;
|
||||
|
||||
// all types must be strictly defined (on mismatch, a compilation error will be triggered anyway)
|
||||
if (v_called->sym_type->has_unknown_inside() || v_current->sym_type->has_unknown_inside())
|
||||
return;
|
||||
// avoid situations like `f(int a, (int,int) b)`, inlining will be cumbersome
|
||||
if (v_current->get_arg_type()->get_width() != static_cast<int>(op_call->right.size()))
|
||||
return;
|
||||
// 'return true;' (false, nil) are (surprisingly) also function calls, with auto_apply=true
|
||||
if (v_called->auto_apply)
|
||||
return;
|
||||
// if an original is marked `pure`, and this one doesn't, it's okay; just check for inline_ref storage
|
||||
if (v_current->is_inline_ref())
|
||||
return;
|
||||
|
||||
// ok, f_current is a wrapper
|
||||
v_current->flags |= SymValFunc::flagWrapsAnotherF;
|
||||
if (verbosity >= 2) {
|
||||
std::cerr << function_name << " -> " << f_called->name() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
static td::RefInt256 calculate_method_id_by_func_name(const std::string &func_name) {
|
||||
unsigned int crc = td::crc16(func_name);
|
||||
return td::make_refint((crc & 0xffff) | 0x10000);
|
||||
}
|
||||
|
||||
// todo rewrite function declaration parsing completely, it's weird
|
||||
void parse_func_def(Lexer& lex) {
|
||||
SrcLocation loc{lex.cur().loc};
|
||||
open_scope(lex);
|
||||
std::vector<TypeExpr*> type_vars;
|
||||
bool is_get_method = false;
|
||||
if (lex.tp() == _Forall) {
|
||||
type_vars = parse_type_var_list(lex);
|
||||
} else if (lex.tp() == _Get) {
|
||||
is_get_method = true;
|
||||
lex.next();
|
||||
}
|
||||
auto ret_type = parse_type(lex);
|
||||
if (lex.tp() != _Ident) {
|
||||
|
@ -1434,47 +1632,80 @@ void parse_func_def(Lexer& lex) {
|
|||
Lexem func_name = lex.cur();
|
||||
lex.next();
|
||||
FormalArgList arg_list = parse_formal_args(lex);
|
||||
bool impure = (lex.tp() == _Impure);
|
||||
if (impure) {
|
||||
bool marked_as_pure = false;
|
||||
if (lex.tp() == _Impure) {
|
||||
static bool warning_shown = false;
|
||||
if (!warning_shown) {
|
||||
lex.cur().loc.show_warning("`impure` specifier is deprecated. All functions are impure by default, use `pure` to mark a function as pure");
|
||||
warning_shown = true;
|
||||
}
|
||||
lex.next();
|
||||
} else if (lex.tp() == _Pure) {
|
||||
marked_as_pure = true;
|
||||
lex.next();
|
||||
}
|
||||
int f = 0;
|
||||
if (lex.tp() == _Inline || lex.tp() == _InlineRef) {
|
||||
f = (lex.tp() == _Inline) ? 1 : 2;
|
||||
int flags_inline = 0;
|
||||
if (lex.tp() == _Inline) {
|
||||
flags_inline = SymValFunc::flagInline;
|
||||
lex.next();
|
||||
} else if (lex.tp() == _InlineRef) {
|
||||
flags_inline = SymValFunc::flagInlineRef;
|
||||
lex.next();
|
||||
}
|
||||
td::RefInt256 method_id;
|
||||
std::string method_name;
|
||||
if (lex.tp() == _MethodId) {
|
||||
if (is_get_method) {
|
||||
lex.cur().error("both `get` and `method_id` are not allowed");
|
||||
}
|
||||
lex.next();
|
||||
if (lex.tp() == '(') {
|
||||
if (lex.tp() == '(') { // method_id(N)
|
||||
lex.expect('(');
|
||||
if (lex.tp() == Lexem::String) {
|
||||
method_name = lex.cur().str;
|
||||
} else if (lex.tp() == Lexem::Number) {
|
||||
method_name = lex.cur().str;
|
||||
method_id = td::string_to_int256(method_name);
|
||||
method_id = td::string_to_int256(lex.cur().str);
|
||||
lex.expect(Lexem::Number);
|
||||
if (method_id.is_null()) {
|
||||
lex.cur().error_at("invalid integer constant `", "`");
|
||||
}
|
||||
} else {
|
||||
throw ParseError{lex.cur().loc, "integer or string method identifier expected"};
|
||||
}
|
||||
lex.next();
|
||||
lex.expect(')');
|
||||
} else {
|
||||
method_name = func_name.str;
|
||||
static bool warning_shown = false;
|
||||
if (!warning_shown) {
|
||||
lex.cur().loc.show_warning("`method_id` specifier is deprecated, use `get` keyword.\nExample: `get int seqno() { ... }`");
|
||||
warning_shown = true;
|
||||
}
|
||||
if (method_id.is_null()) {
|
||||
unsigned crc = td::crc16(method_name);
|
||||
method_id = td::make_refint((crc & 0xffff) | 0x10000);
|
||||
method_id = calculate_method_id_by_func_name(func_name.str);
|
||||
}
|
||||
}
|
||||
if (is_get_method) {
|
||||
tolk_assert(method_id.is_null());
|
||||
method_id = calculate_method_id_by_func_name(func_name.str);
|
||||
for (const SymDef* other : glob_get_methods) {
|
||||
if (!td::cmp(dynamic_cast<const SymValFunc*>(other->value)->method_id, method_id)) {
|
||||
lex.cur().error(PSTRING() << "GET methods hash collision: `" << other->name() << "` and `" + func_name.str + "` produce the same hash. Consider renaming one of these functions.");
|
||||
}
|
||||
}
|
||||
if (lex.tp() != ';' && lex.tp() != '{' && lex.tp() != _Asm) {
|
||||
lex.expect('{', "function body block expected");
|
||||
}
|
||||
TypeExpr* func_type = TypeExpr::new_map(extract_total_arg_type(arg_list), ret_type);
|
||||
func_type = compute_type_closure(func_type, type_vars);
|
||||
if (lex.tp() == _Builtin) {
|
||||
const SymDef* builtin_func = lookup_symbol(func_name.str);
|
||||
const SymValFunc* func_val = builtin_func ? dynamic_cast<SymValFunc*>(builtin_func->value) : nullptr;
|
||||
if (!func_val || !func_val->is_builtin()) {
|
||||
lex.cur().error("`builtin` used for non-builtin function");
|
||||
}
|
||||
#ifdef TOLK_DEBUG
|
||||
// in release, we don't need this check, since `builtin` is used only in stdlib.tolk, which is our responsibility
|
||||
if (!func_val->sym_type->equals_to(func_type) || func_val->is_marked_as_pure() != marked_as_pure) {
|
||||
lex.cur().error("declaration for `builtin` function doesn't match an actual one");
|
||||
}
|
||||
#endif
|
||||
lex.next();
|
||||
lex.expect(';');
|
||||
close_scope(lex);
|
||||
return;
|
||||
}
|
||||
if (lex.tp() != ';' && lex.tp() != '{' && lex.tp() != _Asm) {
|
||||
lex.expect('{', "function body block");
|
||||
}
|
||||
if (verbosity >= 1) {
|
||||
std::cerr << "function " << func_name.str << " : " << func_type << std::endl;
|
||||
}
|
||||
|
@ -1495,7 +1726,7 @@ void parse_func_def(Lexer& lex) {
|
|||
}
|
||||
}
|
||||
if (lex.tp() == ';') {
|
||||
make_new_glob_func(func_sym, func_type, impure);
|
||||
make_new_glob_func(func_sym, func_type, marked_as_pure);
|
||||
lex.next();
|
||||
} else if (lex.tp() == '{') {
|
||||
if (dynamic_cast<SymValAsmFunc*>(func_sym_val)) {
|
||||
|
@ -1508,19 +1739,26 @@ void parse_func_def(Lexer& lex) {
|
|||
lex.cur().error("function `"s + func_name.str + "` has been already defined in an yet-unknown way");
|
||||
}
|
||||
} else {
|
||||
func_sym_code = make_new_glob_func(func_sym, func_type, impure);
|
||||
func_sym_code = make_new_glob_func(func_sym, func_type, marked_as_pure);
|
||||
}
|
||||
if (func_sym_code->code) {
|
||||
lex.cur().error("redefinition of function `"s + func_name.str + "`");
|
||||
}
|
||||
CodeBlob* code = parse_func_body(lex, arg_list, ret_type);
|
||||
if (marked_as_pure && ret_type->get_width() == 0) {
|
||||
lex.cur().error("a pure function should return something, otherwise it will be optimized out anyway");
|
||||
}
|
||||
CodeBlob* code = parse_func_body(lex, arg_list, ret_type, marked_as_pure);
|
||||
code->name = func_name.str;
|
||||
code->loc = loc;
|
||||
// code->print(std::cerr); // !!!DEBUG!!!
|
||||
func_sym_code->code = code;
|
||||
detect_if_function_just_wraps_another(func_sym_code, method_id);
|
||||
} else {
|
||||
Lexem asm_lexem = lex.cur();
|
||||
SymValAsmFunc* asm_func = parse_asm_func_body(lex, func_type, arg_list, ret_type, impure);
|
||||
SymValAsmFunc* asm_func = parse_asm_func_body(lex, func_type, arg_list, ret_type, marked_as_pure);
|
||||
#ifdef TOLK_DEBUG
|
||||
asm_func->name = func_name.str;
|
||||
#endif
|
||||
if (func_sym_val) {
|
||||
if (dynamic_cast<SymValCodeFunc*>(func_sym_val)) {
|
||||
asm_lexem.error("function `"s + func_name.str + "` was already declared as an ordinary function");
|
||||
|
@ -1537,7 +1775,7 @@ void parse_func_def(Lexer& lex) {
|
|||
func_sym->value = asm_func;
|
||||
}
|
||||
if (method_id.not_null()) {
|
||||
auto val = dynamic_cast<SymVal*>(func_sym->value);
|
||||
auto val = dynamic_cast<SymValFunc*>(func_sym->value);
|
||||
if (!val) {
|
||||
lex.cur().error("cannot set method id for unknown function `"s + func_name.str + "`");
|
||||
}
|
||||
|
@ -1548,17 +1786,25 @@ void parse_func_def(Lexer& lex) {
|
|||
val->method_id->to_dec_string() + " to a different value " + method_id->to_dec_string());
|
||||
}
|
||||
}
|
||||
if (f) {
|
||||
auto val = dynamic_cast<SymVal*>(func_sym->value);
|
||||
if (flags_inline) {
|
||||
auto val = dynamic_cast<SymValFunc*>(func_sym->value);
|
||||
if (!val) {
|
||||
lex.cur().error("cannot set unknown function `"s + func_name.str + "` as an inline");
|
||||
}
|
||||
if (!(val->flags & 3)) {
|
||||
val->flags = (short)(val->flags | f);
|
||||
} else if ((val->flags & 3) != f) {
|
||||
if (!val->is_inline() && !val->is_inline_ref()) {
|
||||
val->flags |= flags_inline;
|
||||
} else if ((val->flags & (SymValFunc::flagInline | SymValFunc::flagInlineRef)) != flags_inline) {
|
||||
lex.cur().error("inline mode for `"s + func_name.str + "` changed with respect to a previous declaration");
|
||||
}
|
||||
}
|
||||
if (is_get_method) {
|
||||
auto val = dynamic_cast<SymValFunc*>(func_sym->value);
|
||||
if (!val) {
|
||||
lex.cur().error("cannot set unknown function `"s + func_name.str + "` as a get method");
|
||||
}
|
||||
val->flags |= SymValFunc::flagGetMethod;
|
||||
glob_get_methods.push_back(func_sym);
|
||||
}
|
||||
if (verbosity >= 1) {
|
||||
std::cerr << "new type of function " << func_name.str << " : " << func_type << std::endl;
|
||||
}
|
||||
|
@ -1697,6 +1943,8 @@ void parse_pragma(Lexer& lex) {
|
|||
pragma_allow_post_modification.enable(lex.cur().loc);
|
||||
} else if (pragma_name == pragma_compute_asm_ltr.name()) {
|
||||
pragma_compute_asm_ltr.enable(lex.cur().loc);
|
||||
} else if (pragma_name == pragma_remove_unused_functions.name()) {
|
||||
pragma_remove_unused_functions.enable(lex.cur().loc);
|
||||
} else {
|
||||
lex.cur().error(std::string{"unknown pragma `"} + pragma_name + "`");
|
||||
}
|
||||
|
@ -1728,7 +1976,12 @@ void parse_include(Lexer& lex, const FileDescr* fdescr) {
|
|||
|
||||
bool parse_source(std::istream* is, FileDescr* fdescr) {
|
||||
SourceReader reader{is, fdescr};
|
||||
Lexer lex{reader, true, ";,()[] ~."};
|
||||
Lexer lex{reader, ";,()[] ~."};
|
||||
// previously, FunC had lisp-style comments,
|
||||
// but Tolk supports traditional (slash) comments alongside (lisp-style will be deleted soon)
|
||||
lex.set_comment_tokens(";;", "{-", "-}");
|
||||
lex.set_comment2_tokens("//", "/*", "*/");
|
||||
lex.start_parsing();
|
||||
while (lex.tp() != _Eof) {
|
||||
if (lex.tp() == _PragmaHashtag) {
|
||||
parse_pragma(lex);
|
||||
|
|
|
@ -149,7 +149,11 @@ SymDef* define_global_symbol(sym_idx_t name_idx, bool force_new, const SrcLocati
|
|||
if (found) {
|
||||
return force_new && found->value ? nullptr : found;
|
||||
}
|
||||
return global_sym_def[name_idx] = new SymDef(0, name_idx, loc);
|
||||
found = global_sym_def[name_idx] = new SymDef(0, name_idx, loc);
|
||||
#ifdef TOLK_DEBUG
|
||||
found->sym_name = found->name();
|
||||
#endif
|
||||
return found;
|
||||
}
|
||||
|
||||
SymDef* define_symbol(sym_idx_t name_idx, bool force_new, const SrcLocation& loc) {
|
||||
|
@ -173,6 +177,10 @@ SymDef* define_symbol(sym_idx_t name_idx, bool force_new, const SrcLocation& loc
|
|||
}
|
||||
found = sym_def[name_idx] = new SymDef(scope_level, name_idx, loc);
|
||||
symbol_stack.push_back(std::make_pair(scope_level, SymDef{0, name_idx}));
|
||||
#ifdef TOLK_DEBUG
|
||||
found->sym_name = found->name();
|
||||
symbol_stack.back().second.sym_name = found->name();
|
||||
#endif
|
||||
return found;
|
||||
}
|
||||
|
||||
|
|
|
@ -148,6 +148,9 @@ struct SymDef {
|
|||
sym_idx_t sym_idx;
|
||||
SymValBase* value;
|
||||
SrcLocation loc;
|
||||
#ifdef TOLK_DEBUG
|
||||
std::string sym_name;
|
||||
#endif
|
||||
SymDef(int lvl, sym_idx_t idx, const SrcLocation& _loc = {}, SymValBase* val = 0)
|
||||
: level(lvl), sym_idx(idx), value(val), loc(_loc) {
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "td/utils/Status.h"
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include "vm/boc.h"
|
||||
|
||||
td::Result<std::string> compile_internal(char *config_json) {
|
||||
TRY_RESULT(input_json, td::json_decode(td::MutableSlice(config_json)))
|
||||
|
|
155
tolk/tolk.cpp
155
tolk/tolk.cpp
|
@ -38,9 +38,74 @@ bool stack_layout_comments, op_rewrite_comments, program_envelope, asm_preamble;
|
|||
bool interactive = false;
|
||||
GlobalPragma pragma_allow_post_modification{"allow-post-modification"};
|
||||
GlobalPragma pragma_compute_asm_ltr{"compute-asm-ltr"};
|
||||
GlobalPragma pragma_remove_unused_functions{"remove-unused-functions"};
|
||||
std::string generated_from, boc_output_filename;
|
||||
ReadCallback::Callback read_callback;
|
||||
|
||||
// returns argument type of a function
|
||||
// note, that when a function has multiple arguments, its arg type is a tensor (no arguments — an empty tensor)
|
||||
// in other words, `f(int a, int b)` and `f((int,int) ab)` is the same when we speak about types
|
||||
const TypeExpr *SymValFunc::get_arg_type() const {
|
||||
if (!sym_type)
|
||||
return nullptr;
|
||||
|
||||
tolk_assert(sym_type->constr == TypeExpr::te_Map || sym_type->constr == TypeExpr::te_ForAll);
|
||||
const TypeExpr *te_map = sym_type->constr == TypeExpr::te_ForAll ? sym_type->args[0] : sym_type;
|
||||
const TypeExpr *arg_type = te_map->args[0];
|
||||
|
||||
while (arg_type->constr == TypeExpr::te_Indirect) {
|
||||
arg_type = arg_type->args[0];
|
||||
}
|
||||
return arg_type;
|
||||
}
|
||||
|
||||
|
||||
bool SymValCodeFunc::does_need_codegen() const {
|
||||
// when a function is declared, but not referenced from code in any way, don't generate its body
|
||||
if (!is_really_used && pragma_remove_unused_functions.enabled()) {
|
||||
return false;
|
||||
}
|
||||
// when a function is referenced like `var a = some_fn;` (or in some other non-call way), its continuation should exist
|
||||
if (flags & flagUsedAsNonCall) {
|
||||
return true;
|
||||
}
|
||||
// when a function f() is just `return anotherF(...args)`, it doesn't need to be codegenerated at all,
|
||||
// since all its usages are inlined
|
||||
return !is_just_wrapper_for_another_f();
|
||||
// in the future, we may want to implement a true AST inlining for `inline` functions also
|
||||
}
|
||||
|
||||
void GlobalPragma::enable(SrcLocation loc) {
|
||||
if (deprecated_from_v_) {
|
||||
loc.show_warning(PSTRING() << "#pragma " << name_ <<
|
||||
" is deprecated since Tolk v" << deprecated_from_v_ <<
|
||||
". Please, remove this line from your code.");
|
||||
return;
|
||||
}
|
||||
|
||||
enabled_ = true;
|
||||
locs_.push_back(std::move(loc));
|
||||
}
|
||||
|
||||
void GlobalPragma::check_enable_in_libs() {
|
||||
if (locs_.empty()) {
|
||||
return;
|
||||
}
|
||||
for (const SrcLocation& loc : locs_) {
|
||||
if (loc.fdescr->is_main) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
locs_[0].show_warning(PSTRING() << "#pragma " << name_
|
||||
<< " is enabled in included libraries, it may change the behavior of your code. "
|
||||
<< "Add this #pragma to the main source file to suppress this warning.");
|
||||
}
|
||||
|
||||
void GlobalPragma::always_on_and_deprecated(const char *deprecated_from_v) {
|
||||
deprecated_from_v_ = deprecated_from_v;
|
||||
enabled_ = true;
|
||||
}
|
||||
|
||||
td::Result<std::string> fs_read_callback(ReadCallback::Kind kind, const char* query) {
|
||||
switch (kind) {
|
||||
case ReadCallback::Kind::ReadFile: {
|
||||
|
@ -62,6 +127,55 @@ td::Result<std::string> fs_read_callback(ReadCallback::Kind kind, const char* qu
|
|||
}
|
||||
}
|
||||
|
||||
void mark_function_used_dfs(const std::unique_ptr<Op>& op);
|
||||
|
||||
void mark_function_used(SymValCodeFunc* func_val) {
|
||||
if (!func_val->code || func_val->is_really_used) { // already handled
|
||||
return;
|
||||
}
|
||||
|
||||
func_val->is_really_used = true;
|
||||
mark_function_used_dfs(func_val->code->ops);
|
||||
}
|
||||
|
||||
void mark_global_var_used(SymValGlobVar* glob_val) {
|
||||
glob_val->is_really_used = true;
|
||||
}
|
||||
|
||||
void mark_function_used_dfs(const std::unique_ptr<Op>& op) {
|
||||
if (!op) {
|
||||
return;
|
||||
}
|
||||
// op->fun_ref, despite its name, may actually ref global var
|
||||
// note, that for non-calls, e.g. `var a = some_fn` (Op::_Let), some_fn is Op::_GlobVar
|
||||
// (in other words, fun_ref exists not only for direct Op::_Call, but for non-call references also)
|
||||
if (op->fun_ref) {
|
||||
if (auto* func_val = dynamic_cast<SymValCodeFunc*>(op->fun_ref->value)) {
|
||||
mark_function_used(func_val);
|
||||
} else if (auto* glob_val = dynamic_cast<SymValGlobVar*>(op->fun_ref->value)) {
|
||||
mark_global_var_used(glob_val);
|
||||
} else if (auto* asm_val = dynamic_cast<SymValAsmFunc*>(op->fun_ref->value)) {
|
||||
} else {
|
||||
tolk_assert(false);
|
||||
}
|
||||
}
|
||||
mark_function_used_dfs(op->next);
|
||||
mark_function_used_dfs(op->block0);
|
||||
mark_function_used_dfs(op->block1);
|
||||
}
|
||||
|
||||
void mark_used_symbols() {
|
||||
for (SymDef* func_sym : glob_func) {
|
||||
auto* func_val = dynamic_cast<SymValCodeFunc*>(func_sym->value);
|
||||
std::string name = symbols.get_name(func_sym->sym_idx);
|
||||
if (func_val->method_id.not_null() ||
|
||||
name == "main" || name == "recv_internal" || name == "recv_external" ||
|
||||
name == "run_ticktock" || name == "split_prepare" || name == "split_install") {
|
||||
mark_function_used(func_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* OUTPUT CODE GENERATOR
|
||||
|
@ -76,8 +190,7 @@ void generate_output_func(SymDef* func_sym, std::ostream &outs, std::ostream &er
|
|||
errs << "\n\n=========================\nfunction " << name << " : " << func_val->get_type() << std::endl;
|
||||
}
|
||||
if (!func_val->code) {
|
||||
errs << "( function `" << name << "` undefined )\n";
|
||||
throw ParseError(func_sym->loc, name);
|
||||
throw ParseError(func_sym->loc, "function `" + name + "` is just declared, not implemented");
|
||||
} else {
|
||||
CodeBlob& code = *(func_val->code);
|
||||
if (verbosity >= 3) {
|
||||
|
@ -122,12 +235,10 @@ void generate_output_func(SymDef* func_sym, std::ostream &outs, std::ostream &er
|
|||
if (verbosity >= 2) {
|
||||
errs << "\n---------- resulting code for " << name << " -------------\n";
|
||||
}
|
||||
bool inline_func = (func_val->flags & 1);
|
||||
bool inline_ref = (func_val->flags & 2);
|
||||
const char* modifier = "";
|
||||
if (inline_func) {
|
||||
if (func_val->is_inline()) {
|
||||
modifier = "INLINE";
|
||||
} else if (inline_ref) {
|
||||
} else if (func_val->is_inline_ref()) {
|
||||
modifier = "REF";
|
||||
}
|
||||
outs << std::string(indent * 2, ' ') << name << " PROC" << modifier << ":<{\n";
|
||||
|
@ -138,12 +249,10 @@ void generate_output_func(SymDef* func_sym, std::ostream &outs, std::ostream &er
|
|||
if (opt_level < 2) {
|
||||
mode |= Stack::_DisableOpt;
|
||||
}
|
||||
auto fv = dynamic_cast<const SymValCodeFunc*>(func_sym->value);
|
||||
// Flags: 1 - inline, 2 - inline_ref
|
||||
if (fv && (fv->flags & 1) && code.ops->noreturn()) {
|
||||
if (func_val->is_inline() && code.ops->noreturn()) {
|
||||
mode |= Stack::_InlineFunc;
|
||||
}
|
||||
if (fv && (fv->flags & 3)) {
|
||||
if (func_val->is_inline() || func_val->is_inline_ref()) {
|
||||
mode |= Stack::_InlineAny;
|
||||
}
|
||||
code.generate_code(outs, mode, indent + 1);
|
||||
|
@ -162,9 +271,17 @@ int generate_output(std::ostream &outs, std::ostream &errs) {
|
|||
if (program_envelope) {
|
||||
outs << "PROGRAM{\n";
|
||||
}
|
||||
mark_used_symbols();
|
||||
for (SymDef* func_sym : glob_func) {
|
||||
SymValCodeFunc* func_val = dynamic_cast<SymValCodeFunc*>(func_sym->value);
|
||||
tolk_assert(func_val);
|
||||
if (!func_val->does_need_codegen()) {
|
||||
if (verbosity >= 2) {
|
||||
errs << func_sym->name() << ": code not generated, function does not need codegen\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string name = symbols.get_name(func_sym->sym_idx);
|
||||
outs << std::string(indent * 2, ' ');
|
||||
if (func_val->method_id.is_null()) {
|
||||
|
@ -174,12 +291,23 @@ int generate_output(std::ostream &outs, std::ostream &errs) {
|
|||
}
|
||||
}
|
||||
for (SymDef* gvar_sym : glob_vars) {
|
||||
tolk_assert(dynamic_cast<SymValGlobVar*>(gvar_sym->value));
|
||||
auto* glob_val = dynamic_cast<SymValGlobVar*>(gvar_sym->value);
|
||||
tolk_assert(glob_val);
|
||||
if (!glob_val->is_really_used && pragma_remove_unused_functions.enabled()) {
|
||||
if (verbosity >= 2) {
|
||||
errs << gvar_sym->name() << ": variable not generated, it's unused\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
std::string name = symbols.get_name(gvar_sym->sym_idx);
|
||||
outs << std::string(indent * 2, ' ') << "DECLGLOBVAR " << name << "\n";
|
||||
}
|
||||
int errors = 0;
|
||||
for (SymDef* func_sym : glob_func) {
|
||||
SymValCodeFunc* func_val = dynamic_cast<SymValCodeFunc*>(func_sym->value);
|
||||
if (!func_val->does_need_codegen()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
generate_output_func(func_sym, outs, errs);
|
||||
} catch (Error& err) {
|
||||
|
@ -217,6 +345,8 @@ int tolk_proceed(const std::vector<std::string> &sources, std::ostream &outs, st
|
|||
|
||||
define_keywords();
|
||||
define_builtins();
|
||||
pragma_allow_post_modification.always_on_and_deprecated("0.5.0");
|
||||
pragma_compute_asm_ltr.always_on_and_deprecated("0.5.0");
|
||||
|
||||
int ok = 0, proc = 0;
|
||||
try {
|
||||
|
@ -235,8 +365,7 @@ int tolk_proceed(const std::vector<std::string> &sources, std::ostream &outs, st
|
|||
if (!proc) {
|
||||
throw Fatal{"no source files, no output"};
|
||||
}
|
||||
pragma_allow_post_modification.check_enable_in_libs();
|
||||
pragma_compute_asm_ltr.check_enable_in_libs();
|
||||
pragma_remove_unused_functions.check_enable_in_libs();
|
||||
return generate_output(outs, errs);
|
||||
} catch (Fatal& fatal) {
|
||||
errs << "fatal: " << fatal << std::endl;
|
||||
|
|
242
tolk/tolk.h
242
tolk/tolk.h
|
@ -106,12 +106,15 @@ enum Keyword {
|
|||
_Forall,
|
||||
_Asm,
|
||||
_Impure,
|
||||
_Pure,
|
||||
_Global,
|
||||
_Extern,
|
||||
_Inline,
|
||||
_InlineRef,
|
||||
_Builtin,
|
||||
_AutoApply,
|
||||
_MethodId,
|
||||
_Get,
|
||||
_Operator,
|
||||
_Infix,
|
||||
_Infixl,
|
||||
|
@ -147,8 +150,8 @@ class IdSc {
|
|||
*/
|
||||
|
||||
struct TypeExpr {
|
||||
enum te_type { te_Unknown, te_Var, te_Indirect, te_Atomic, te_Tensor, te_Tuple, te_Map, te_Type, te_ForAll } constr;
|
||||
enum {
|
||||
enum te_type { te_Unknown, te_Var, te_Indirect, te_Atomic, te_Tensor, te_Tuple, te_Map, te_ForAll } constr;
|
||||
enum AtomicType {
|
||||
_Int = Keyword::_Int,
|
||||
_Cell = Keyword::_Cell,
|
||||
_Slice = Keyword::_Slice,
|
||||
|
@ -214,9 +217,11 @@ struct TypeExpr {
|
|||
void compute_width();
|
||||
bool recompute_width();
|
||||
void show_width(std::ostream& os);
|
||||
std::ostream& print(std::ostream& os, int prio = 0);
|
||||
std::ostream& print(std::ostream& os, int prio = 0) const;
|
||||
void replace_with(TypeExpr* te2);
|
||||
int extract_components(std::vector<TypeExpr*>& comp_list);
|
||||
bool equals_to(const TypeExpr* rhs) const;
|
||||
bool has_unknown_inside() const;
|
||||
static int holes, type_vars;
|
||||
static TypeExpr* new_hole() {
|
||||
return new TypeExpr{te_Unknown, ++holes};
|
||||
|
@ -528,7 +533,7 @@ class ListIterator {
|
|||
struct Stack;
|
||||
|
||||
struct Op {
|
||||
enum {
|
||||
enum OpKind {
|
||||
_Undef,
|
||||
_Nop,
|
||||
_Call,
|
||||
|
@ -547,13 +552,13 @@ struct Op {
|
|||
_Repeat,
|
||||
_Again,
|
||||
_TryCatch,
|
||||
_SliceConst
|
||||
_SliceConst,
|
||||
};
|
||||
int cl;
|
||||
enum { _Disabled = 1, _Reachable = 2, _NoReturn = 4, _ImpureR = 8, _ImpureW = 16, _Impure = 24 };
|
||||
OpKind cl;
|
||||
enum { _Disabled = 1, _NoReturn = 4, _Impure = 24 };
|
||||
int flags;
|
||||
std::unique_ptr<Op> next;
|
||||
SymDef* fun_ref;
|
||||
SymDef* fun_ref; // despite its name, it may actually ref global var; applicable not only to Op::_Call, but for other kinds also
|
||||
SrcLocation where;
|
||||
VarDescrList var_info;
|
||||
std::vector<VarDescr> args;
|
||||
|
@ -561,41 +566,41 @@ struct Op {
|
|||
std::unique_ptr<Op> block0, block1;
|
||||
td::RefInt256 int_const;
|
||||
std::string str_const;
|
||||
Op(const SrcLocation& _where = {}, int _cl = _Undef) : cl(_cl), flags(0), fun_ref(nullptr), where(_where) {
|
||||
Op(const SrcLocation& _where = {}, OpKind _cl = _Undef) : cl(_cl), flags(0), fun_ref(nullptr), where(_where) {
|
||||
}
|
||||
Op(const SrcLocation& _where, int _cl, const std::vector<var_idx_t>& _left)
|
||||
Op(const SrcLocation& _where, OpKind _cl, const std::vector<var_idx_t>& _left)
|
||||
: cl(_cl), flags(0), fun_ref(nullptr), where(_where), left(_left) {
|
||||
}
|
||||
Op(const SrcLocation& _where, int _cl, std::vector<var_idx_t>&& _left)
|
||||
Op(const SrcLocation& _where, OpKind _cl, std::vector<var_idx_t>&& _left)
|
||||
: cl(_cl), flags(0), fun_ref(nullptr), where(_where), left(std::move(_left)) {
|
||||
}
|
||||
Op(const SrcLocation& _where, int _cl, const std::vector<var_idx_t>& _left, td::RefInt256 _const)
|
||||
Op(const SrcLocation& _where, OpKind _cl, const std::vector<var_idx_t>& _left, td::RefInt256 _const)
|
||||
: cl(_cl), flags(0), fun_ref(nullptr), where(_where), left(_left), int_const(_const) {
|
||||
}
|
||||
Op(const SrcLocation& _where, int _cl, const std::vector<var_idx_t>& _left, std::string _const)
|
||||
Op(const SrcLocation& _where, OpKind _cl, const std::vector<var_idx_t>& _left, std::string _const)
|
||||
: cl(_cl), flags(0), fun_ref(nullptr), where(_where), left(_left), str_const(_const) {
|
||||
}
|
||||
Op(const SrcLocation& _where, int _cl, const std::vector<var_idx_t>& _left, const std::vector<var_idx_t>& _right,
|
||||
Op(const SrcLocation& _where, OpKind _cl, const std::vector<var_idx_t>& _left, const std::vector<var_idx_t>& _right,
|
||||
SymDef* _fun = nullptr)
|
||||
: cl(_cl), flags(0), fun_ref(_fun), where(_where), left(_left), right(_right) {
|
||||
}
|
||||
Op(const SrcLocation& _where, int _cl, std::vector<var_idx_t>&& _left, std::vector<var_idx_t>&& _right,
|
||||
Op(const SrcLocation& _where, OpKind _cl, std::vector<var_idx_t>&& _left, std::vector<var_idx_t>&& _right,
|
||||
SymDef* _fun = nullptr)
|
||||
: cl(_cl), flags(0), fun_ref(_fun), where(_where), left(std::move(_left)), right(std::move(_right)) {
|
||||
}
|
||||
bool disabled() const {
|
||||
return flags & _Disabled;
|
||||
}
|
||||
bool enabled() const {
|
||||
return !disabled();
|
||||
}
|
||||
void disable() {
|
||||
flags |= _Disabled;
|
||||
}
|
||||
bool unreachable() {
|
||||
return !(flags & _Reachable);
|
||||
}
|
||||
void flags_set_clear(int set, int clear);
|
||||
|
||||
bool disabled() const { return flags & _Disabled; }
|
||||
void set_disabled() { flags |= _Disabled; }
|
||||
void set_disabled(bool flag);
|
||||
|
||||
bool noreturn() const { return flags & _NoReturn; }
|
||||
bool set_noreturn() { flags |= _NoReturn; return true; }
|
||||
bool set_noreturn(bool flag);
|
||||
|
||||
bool impure() const { return flags & _Impure; }
|
||||
void set_impure(const CodeBlob &code);
|
||||
void set_impure(const CodeBlob &code, bool flag);
|
||||
|
||||
void show(std::ostream& os, const std::vector<TmpVar>& vars, std::string pfx = "", int mode = 0) const;
|
||||
void show_var_list(std::ostream& os, const std::vector<var_idx_t>& idx_list, const std::vector<TmpVar>& vars) const;
|
||||
void show_var_list(std::ostream& os, const std::vector<VarDescr>& list, const std::vector<TmpVar>& vars) const;
|
||||
|
@ -611,17 +616,10 @@ struct Op {
|
|||
bool set_var_info_except(VarDescrList&& new_var_info, const std::vector<var_idx_t>& var_list);
|
||||
void prepare_args(VarDescrList values);
|
||||
VarDescrList fwd_analyze(VarDescrList values);
|
||||
bool set_noreturn(bool nr);
|
||||
bool mark_noreturn();
|
||||
bool noreturn() const {
|
||||
return flags & _NoReturn;
|
||||
}
|
||||
bool is_empty() const {
|
||||
return cl == _Nop && !next;
|
||||
}
|
||||
bool is_pure() const {
|
||||
return !(flags & _Impure);
|
||||
}
|
||||
bool generate_code_step(Stack& stack);
|
||||
void generate_code_all(Stack& stack);
|
||||
Op& last() {
|
||||
|
@ -682,7 +680,7 @@ typedef std::vector<FormalArg> FormalArgList;
|
|||
struct AsmOpList;
|
||||
|
||||
struct CodeBlob {
|
||||
enum { _AllowPostModification = 1, _ComputeAsmLtr = 2 };
|
||||
enum { _ForbidImpure = 4 };
|
||||
int var_cnt, in_var_cnt, op_cnt;
|
||||
TypeExpr* ret_type;
|
||||
std::string name;
|
||||
|
@ -726,7 +724,6 @@ struct CodeBlob {
|
|||
pop_cur();
|
||||
}
|
||||
void simplify_var_types();
|
||||
void flags_set_clear(int set, int clear);
|
||||
void prune_unreachable_code();
|
||||
void fwd_analyze();
|
||||
void mark_noreturn();
|
||||
|
@ -748,48 +745,75 @@ struct CodeBlob {
|
|||
|
||||
struct SymVal : SymValBase {
|
||||
TypeExpr* sym_type;
|
||||
td::RefInt256 method_id;
|
||||
bool impure;
|
||||
bool auto_apply{false};
|
||||
short flags; // +1 = inline, +2 = inline_ref
|
||||
SymVal(int _type, int _idx, TypeExpr* _stype = nullptr, bool _impure = false)
|
||||
: SymValBase(_type, _idx), sym_type(_stype), impure(_impure), flags(0) {
|
||||
SymVal(int _type, int _idx, TypeExpr* _stype = nullptr)
|
||||
: SymValBase(_type, _idx), sym_type(_stype) {
|
||||
}
|
||||
~SymVal() override = default;
|
||||
TypeExpr* get_type() const {
|
||||
return sym_type;
|
||||
}
|
||||
virtual const std::vector<int>* get_arg_order() const {
|
||||
return nullptr;
|
||||
}
|
||||
virtual const std::vector<int>* get_ret_order() const {
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
struct SymValFunc : SymVal {
|
||||
enum SymValFlag {
|
||||
flagInline = 1, // function marked `inline`
|
||||
flagInlineRef = 2, // function marked `inline_ref`
|
||||
flagWrapsAnotherF = 4, // (T) thisF(...args) { return anotherF(...args); } (calls to thisF will be replaced)
|
||||
flagUsedAsNonCall = 8, // used not only as `f()`, but as a 1-st class function (assigned to var, pushed to tuple, etc.)
|
||||
flagMarkedAsPure = 16, // declared as `pure`, can't call impure and access globals, unused invocations are optimized out
|
||||
flagBuiltinFunction = 32, // was created via `define_builtin_func()`, not from source code
|
||||
flagGetMethod = 64, // was declared via `get T func()`, method_id is auto-assigned
|
||||
};
|
||||
|
||||
td::RefInt256 method_id; // todo why int256? it's small
|
||||
int flags{0};
|
||||
std::vector<int> arg_order, ret_order;
|
||||
#ifdef TOLK_DEBUG
|
||||
std::string name; // seeing function name in debugger makes it much easier to delve into Tolk sources
|
||||
#endif
|
||||
~SymValFunc() override = default;
|
||||
SymValFunc(int val, TypeExpr* _ft, bool _impure = false) : SymVal(_Func, val, _ft, _impure) {
|
||||
}
|
||||
SymValFunc(int val, TypeExpr* _ft, std::initializer_list<int> _arg_order, std::initializer_list<int> _ret_order = {},
|
||||
bool _impure = false)
|
||||
: SymVal(_Func, val, _ft, _impure), arg_order(_arg_order), ret_order(_ret_order) {
|
||||
SymValFunc(int val, TypeExpr* _ft, bool marked_as_pure)
|
||||
: SymVal(_Func, val, _ft), flags(marked_as_pure ? flagMarkedAsPure : 0) {}
|
||||
SymValFunc(int val, TypeExpr* _ft, std::initializer_list<int> _arg_order, std::initializer_list<int> _ret_order, bool marked_as_pure)
|
||||
: SymVal(_Func, val, _ft), flags(marked_as_pure ? flagMarkedAsPure : 0), arg_order(_arg_order), ret_order(_ret_order) {
|
||||
}
|
||||
|
||||
const std::vector<int>* get_arg_order() const override {
|
||||
const std::vector<int>* get_arg_order() const {
|
||||
return arg_order.empty() ? nullptr : &arg_order;
|
||||
}
|
||||
const std::vector<int>* get_ret_order() const override {
|
||||
const std::vector<int>* get_ret_order() const {
|
||||
return ret_order.empty() ? nullptr : &ret_order;
|
||||
}
|
||||
const TypeExpr* get_arg_type() const;
|
||||
|
||||
bool is_inline() const {
|
||||
return flags & flagInline;
|
||||
}
|
||||
bool is_inline_ref() const {
|
||||
return flags & flagInlineRef;
|
||||
}
|
||||
bool is_just_wrapper_for_another_f() const {
|
||||
return flags & flagWrapsAnotherF;
|
||||
}
|
||||
bool is_marked_as_pure() const {
|
||||
return flags & flagMarkedAsPure;
|
||||
}
|
||||
bool is_builtin() const {
|
||||
return flags & flagBuiltinFunction;
|
||||
}
|
||||
bool is_get_method() const {
|
||||
return flags & flagGetMethod;
|
||||
}
|
||||
};
|
||||
|
||||
struct SymValCodeFunc : SymValFunc {
|
||||
CodeBlob* code;
|
||||
bool is_really_used{false}; // calculated via dfs; unused functions are not codegenerated
|
||||
~SymValCodeFunc() override = default;
|
||||
SymValCodeFunc(int val, TypeExpr* _ft, bool _impure = false) : SymValFunc(val, _ft, _impure), code(nullptr) {
|
||||
SymValCodeFunc(int val, TypeExpr* _ft, bool marked_as_pure) : SymValFunc(val, _ft, marked_as_pure), code(nullptr) {
|
||||
}
|
||||
bool does_need_codegen() const;
|
||||
};
|
||||
|
||||
struct SymValType : SymValBase {
|
||||
|
@ -805,6 +829,10 @@ struct SymValType : SymValBase {
|
|||
struct SymValGlobVar : SymValBase {
|
||||
TypeExpr* sym_type;
|
||||
int out_idx{0};
|
||||
bool is_really_used{false}; // calculated via dfs from used functions; unused globals are not codegenerated
|
||||
#ifdef TOLK_DEBUG
|
||||
std::string name; // seeing variable name in debugger makes it much easier to delve into Tolk sources
|
||||
#endif
|
||||
SymValGlobVar(int val, TypeExpr* gvtype, int oidx = 0)
|
||||
: SymValBase(_GlobVar, val), sym_type(gvtype), out_idx(oidx) {
|
||||
}
|
||||
|
@ -839,7 +867,7 @@ struct SymValConst : SymValBase {
|
|||
};
|
||||
|
||||
extern int glob_func_cnt, undef_func_cnt, glob_var_cnt;
|
||||
extern std::vector<SymDef*> glob_func, glob_vars;
|
||||
extern std::vector<SymDef*> glob_func, glob_vars, glob_get_methods;
|
||||
extern std::set<std::string> prohibited_var_names;
|
||||
|
||||
/*
|
||||
|
@ -891,7 +919,7 @@ extern std::stack<SrcLocation> inclusion_locations;
|
|||
*/
|
||||
|
||||
struct Expr {
|
||||
enum {
|
||||
enum ExprCls {
|
||||
_None,
|
||||
_Apply,
|
||||
_VarApply,
|
||||
|
@ -900,18 +928,18 @@ struct Expr {
|
|||
_Tensor,
|
||||
_Const,
|
||||
_Var,
|
||||
_Glob,
|
||||
_GlobFunc,
|
||||
_GlobVar,
|
||||
_Letop,
|
||||
_LetFirst,
|
||||
_Hole,
|
||||
_Type,
|
||||
_CondExpr,
|
||||
_SliceConst
|
||||
_SliceConst,
|
||||
};
|
||||
int cls;
|
||||
ExprCls cls;
|
||||
int val{0};
|
||||
enum { _IsType = 1, _IsRvalue = 2, _IsLvalue = 4, _IsHole = 8, _IsNewVar = 16, _IsImpure = 32 };
|
||||
enum { _IsType = 1, _IsRvalue = 2, _IsLvalue = 4, _IsImpure = 32, _IsInsideParenthesis = 64 };
|
||||
int flags{0};
|
||||
SrcLocation here;
|
||||
td::RefInt256 intval;
|
||||
|
@ -919,19 +947,19 @@ struct Expr {
|
|||
SymDef* sym{nullptr};
|
||||
TypeExpr* e_type{nullptr};
|
||||
std::vector<Expr*> args;
|
||||
Expr(int c = _None) : cls(c) {
|
||||
explicit Expr(ExprCls c = _None) : cls(c) {
|
||||
}
|
||||
Expr(int c, const SrcLocation& loc) : cls(c), here(loc) {
|
||||
Expr(ExprCls c, const SrcLocation& loc) : cls(c), here(loc) {
|
||||
}
|
||||
Expr(int c, std::vector<Expr*> _args) : cls(c), args(std::move(_args)) {
|
||||
Expr(ExprCls c, std::vector<Expr*> _args) : cls(c), args(std::move(_args)) {
|
||||
}
|
||||
Expr(int c, std::initializer_list<Expr*> _arglist) : cls(c), args(std::move(_arglist)) {
|
||||
Expr(ExprCls c, std::initializer_list<Expr*> _arglist) : cls(c), args(std::move(_arglist)) {
|
||||
}
|
||||
Expr(int c, SymDef* _sym, std::initializer_list<Expr*> _arglist) : cls(c), sym(_sym), args(std::move(_arglist)) {
|
||||
Expr(ExprCls c, SymDef* _sym, std::initializer_list<Expr*> _arglist) : cls(c), sym(_sym), args(std::move(_arglist)) {
|
||||
}
|
||||
Expr(int c, SymDef* _sym, std::vector<Expr*> _arglist) : cls(c), sym(_sym), args(std::move(_arglist)) {
|
||||
Expr(ExprCls c, SymDef* _sym, std::vector<Expr*> _arglist) : cls(c), sym(_sym), args(std::move(_arglist)) {
|
||||
}
|
||||
Expr(int c, sym_idx_t name_idx, std::initializer_list<Expr*> _arglist);
|
||||
Expr(ExprCls c, sym_idx_t name_idx, std::initializer_list<Expr*> _arglist);
|
||||
~Expr() {
|
||||
for (auto& arg_ptr : args) {
|
||||
delete arg_ptr;
|
||||
|
@ -953,6 +981,9 @@ struct Expr {
|
|||
bool is_type() const {
|
||||
return flags & _IsType;
|
||||
}
|
||||
bool is_inside_parenthesis() const {
|
||||
return flags & _IsInsideParenthesis;
|
||||
}
|
||||
bool is_type_apply() const {
|
||||
return cls == _TypeApply;
|
||||
}
|
||||
|
@ -972,7 +1003,6 @@ struct Expr {
|
|||
int define_new_vars(CodeBlob& code);
|
||||
int predefine_vars();
|
||||
std::vector<var_idx_t> pre_compile(CodeBlob& code, std::vector<std::pair<SymDef*, var_idx_t>>* lval_globs = nullptr) const;
|
||||
static std::vector<var_idx_t> pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rhs, const SrcLocation& here);
|
||||
var_idx_t new_tmp(CodeBlob& code) const;
|
||||
std::vector<var_idx_t> new_tmp_vect(CodeBlob& code) const {
|
||||
return {new_tmp(code)};
|
||||
|
@ -993,9 +1023,9 @@ using Const = td::RefInt256;
|
|||
|
||||
struct AsmOp {
|
||||
enum Type { a_none, a_xchg, a_push, a_pop, a_const, a_custom, a_magic };
|
||||
int t{a_none};
|
||||
Type t{a_none};
|
||||
int indent{0};
|
||||
int a, b, c;
|
||||
int a, b;
|
||||
bool gconst{false};
|
||||
std::string op;
|
||||
td::RefInt256 origin;
|
||||
|
@ -1005,26 +1035,22 @@ struct AsmOp {
|
|||
}
|
||||
};
|
||||
AsmOp() = default;
|
||||
AsmOp(int _t) : t(_t) {
|
||||
AsmOp(Type _t) : t(_t) {
|
||||
}
|
||||
AsmOp(int _t, std::string _op) : t(_t), op(std::move(_op)) {
|
||||
AsmOp(Type _t, std::string _op) : t(_t), op(std::move(_op)) {
|
||||
}
|
||||
AsmOp(int _t, int _a) : t(_t), a(_a) {
|
||||
AsmOp(Type _t, int _a) : t(_t), a(_a) {
|
||||
}
|
||||
AsmOp(int _t, int _a, std::string _op) : t(_t), a(_a), op(std::move(_op)) {
|
||||
AsmOp(Type _t, int _a, std::string _op) : t(_t), a(_a), op(std::move(_op)) {
|
||||
}
|
||||
AsmOp(int _t, int _a, int _b) : t(_t), a(_a), b(_b) {
|
||||
AsmOp(Type _t, int _a, int _b) : t(_t), a(_a), b(_b) {
|
||||
}
|
||||
AsmOp(int _t, int _a, int _b, std::string _op) : t(_t), a(_a), b(_b), op(std::move(_op)) {
|
||||
AsmOp(Type _t, int _a, int _b, std::string _op) : t(_t), a(_a), b(_b), op(std::move(_op)) {
|
||||
compute_gconst();
|
||||
}
|
||||
AsmOp(int _t, int _a, int _b, std::string _op, td::RefInt256 x) : t(_t), a(_a), b(_b), op(std::move(_op)), origin(x) {
|
||||
AsmOp(Type _t, int _a, int _b, std::string _op, td::RefInt256 x) : t(_t), a(_a), b(_b), op(std::move(_op)), origin(x) {
|
||||
compute_gconst();
|
||||
}
|
||||
AsmOp(int _t, int _a, int _b, int _c) : t(_t), a(_a), b(_b), c(_c) {
|
||||
}
|
||||
AsmOp(int _t, int _a, int _b, int _c, std::string _op) : t(_t), a(_a), b(_b), c(_c), op(std::move(_op)) {
|
||||
}
|
||||
void out(std::ostream& os) const;
|
||||
void out_indent_nl(std::ostream& os, bool no_nl = false) const;
|
||||
std::string to_string() const;
|
||||
|
@ -1680,8 +1706,8 @@ inline simple_compile_func_t make_simple_compile(AsmOp op) {
|
|||
return [op](std::vector<VarDescr>& out, std::vector<VarDescr>& in, const SrcLocation&) -> AsmOp { return op; };
|
||||
}
|
||||
|
||||
inline compile_func_t make_ext_compile(std::vector<AsmOp> ops) {
|
||||
return [ops = std::move(ops)](AsmOpList & dest, std::vector<VarDescr> & out, std::vector<VarDescr> & in)->bool {
|
||||
inline compile_func_t make_ext_compile(std::vector<AsmOp>&& ops) {
|
||||
return [ops = std::move(ops)](AsmOpList& dest, std::vector<VarDescr>& out, std::vector<VarDescr>& in)->bool {
|
||||
return dest.append(ops);
|
||||
};
|
||||
}
|
||||
|
@ -1696,25 +1722,22 @@ struct SymValAsmFunc : SymValFunc {
|
|||
compile_func_t ext_compile;
|
||||
td::uint64 crc;
|
||||
~SymValAsmFunc() override = default;
|
||||
SymValAsmFunc(TypeExpr* ft, const AsmOp& _macro, bool impure = false)
|
||||
: SymValFunc(-1, ft, impure), simple_compile(make_simple_compile(_macro)) {
|
||||
SymValAsmFunc(TypeExpr* ft, std::vector<AsmOp>&& _macro, bool marked_as_pure)
|
||||
: SymValFunc(-1, ft, marked_as_pure), ext_compile(make_ext_compile(std::move(_macro))) {
|
||||
}
|
||||
SymValAsmFunc(TypeExpr* ft, std::vector<AsmOp> _macro, bool impure = false)
|
||||
: SymValFunc(-1, ft, impure), ext_compile(make_ext_compile(std::move(_macro))) {
|
||||
SymValAsmFunc(TypeExpr* ft, simple_compile_func_t _compile, bool marked_as_pure)
|
||||
: SymValFunc(-1, ft, marked_as_pure), simple_compile(std::move(_compile)) {
|
||||
}
|
||||
SymValAsmFunc(TypeExpr* ft, simple_compile_func_t _compile, bool impure = false)
|
||||
: SymValFunc(-1, ft, impure), simple_compile(std::move(_compile)) {
|
||||
}
|
||||
SymValAsmFunc(TypeExpr* ft, compile_func_t _compile, bool impure = false)
|
||||
: SymValFunc(-1, ft, impure), ext_compile(std::move(_compile)) {
|
||||
SymValAsmFunc(TypeExpr* ft, compile_func_t _compile, bool marked_as_pure)
|
||||
: SymValFunc(-1, ft, marked_as_pure), ext_compile(std::move(_compile)) {
|
||||
}
|
||||
SymValAsmFunc(TypeExpr* ft, simple_compile_func_t _compile, std::initializer_list<int> arg_order,
|
||||
std::initializer_list<int> ret_order = {}, bool impure = false)
|
||||
: SymValFunc(-1, ft, arg_order, ret_order, impure), simple_compile(std::move(_compile)) {
|
||||
std::initializer_list<int> ret_order = {}, bool marked_as_pure = false)
|
||||
: SymValFunc(-1, ft, arg_order, ret_order, marked_as_pure), simple_compile(std::move(_compile)) {
|
||||
}
|
||||
SymValAsmFunc(TypeExpr* ft, compile_func_t _compile, std::initializer_list<int> arg_order,
|
||||
std::initializer_list<int> ret_order = {}, bool impure = false)
|
||||
: SymValFunc(-1, ft, arg_order, ret_order, impure), ext_compile(std::move(_compile)) {
|
||||
std::initializer_list<int> ret_order = {}, bool marked_as_pure = false)
|
||||
: SymValFunc(-1, ft, arg_order, ret_order, marked_as_pure), ext_compile(std::move(_compile)) {
|
||||
}
|
||||
bool compile(AsmOpList& dest, std::vector<VarDescr>& out, std::vector<VarDescr>& in, const SrcLocation& where) const;
|
||||
};
|
||||
|
@ -1747,30 +1770,17 @@ class GlobalPragma {
|
|||
bool enabled() const {
|
||||
return enabled_;
|
||||
}
|
||||
void enable(SrcLocation loc) {
|
||||
enabled_ = true;
|
||||
locs_.push_back(std::move(loc));
|
||||
}
|
||||
void check_enable_in_libs() {
|
||||
if (locs_.empty()) {
|
||||
return;
|
||||
}
|
||||
for (const SrcLocation& loc : locs_) {
|
||||
if (loc.fdescr->is_main) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
locs_[0].show_warning(PSTRING() << "#pragma " << name_
|
||||
<< " is enabled in included libraries, it may change the behavior of your code. "
|
||||
<< "Add this #pragma to the main source file to suppress this warning.");
|
||||
}
|
||||
void enable(SrcLocation loc);
|
||||
void check_enable_in_libs();
|
||||
void always_on_and_deprecated(const char *deprecated_from_v);
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
bool enabled_ = false;
|
||||
const char *deprecated_from_v_ = nullptr;
|
||||
std::vector<SrcLocation> locs_;
|
||||
};
|
||||
extern GlobalPragma pragma_allow_post_modification, pragma_compute_asm_ltr;
|
||||
extern GlobalPragma pragma_allow_post_modification, pragma_compute_asm_ltr, pragma_remove_unused_functions;
|
||||
|
||||
/*
|
||||
*
|
||||
|
|
|
@ -113,6 +113,39 @@ int TypeExpr::extract_components(std::vector<TypeExpr*>& comp_list) {
|
|||
return res;
|
||||
}
|
||||
|
||||
bool TypeExpr::equals_to(const TypeExpr *rhs) const {
|
||||
const TypeExpr *l = this;
|
||||
const TypeExpr *r = rhs;
|
||||
while (l->constr == te_Indirect)
|
||||
l = l->args[0];
|
||||
while (r->constr == te_Indirect)
|
||||
r = r->args[0];
|
||||
|
||||
bool eq = l->constr == r->constr && l->value == r->value &&
|
||||
l->minw == r->minw && l->maxw == r->maxw &&
|
||||
l->was_forall_var == r->was_forall_var &&
|
||||
l->args.size() == r->args.size();
|
||||
if (!eq)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < static_cast<int>(l->args.size()); ++i) {
|
||||
if (!l->args[i]->equals_to(r->args[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TypeExpr::has_unknown_inside() const {
|
||||
if (constr == te_Unknown)
|
||||
return true;
|
||||
|
||||
for (const TypeExpr* inner : args) {
|
||||
if (inner->has_unknown_inside())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TypeExpr* TypeExpr::new_map(TypeExpr* from, TypeExpr* to) {
|
||||
return new TypeExpr{te_Map, std::vector<TypeExpr*>{from, to}};
|
||||
}
|
||||
|
@ -207,7 +240,7 @@ std::ostream& operator<<(std::ostream& os, TypeExpr* type_expr) {
|
|||
return type_expr->print(os);
|
||||
}
|
||||
|
||||
std::ostream& TypeExpr::print(std::ostream& os, int lex_level) {
|
||||
std::ostream& TypeExpr::print(std::ostream& os, int lex_level) const {
|
||||
switch (constr) {
|
||||
case te_Unknown:
|
||||
return os << "??" << value;
|
||||
|
|
|
@ -386,7 +386,7 @@ class TonlibCli : public td::actor::Actor {
|
|||
td::TerminalIO::out() << "sendfile <filename>\tLoad a serialized message from <filename> and send it to server\n";
|
||||
td::TerminalIO::out() << "setconfig|validateconfig <path> [<name>] [<use_callback>] [<force>] - set or validate "
|
||||
"lite server config\n";
|
||||
td::TerminalIO::out() << "runmethod <addr> <method-id> <params>...\tRuns GET method <method-id> of account "
|
||||
td::TerminalIO::out() << "runmethod <addr> <name> <params>...\tRuns GET method <name> of account "
|
||||
"<addr> with specified parameters\n";
|
||||
td::TerminalIO::out() << "getstate <key_id>\tget state of wallet with requested key\n";
|
||||
td::TerminalIO::out() << "getstatebytransaction <key_id> <lt> <hash>\tget state of wallet with requested key after transaction with local time <lt> and hash <hash> (base64url)\n";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue