1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

[Tolk] Get rid of ~tilda with mutate and self methods

This is a very big change.
If FunC has `.methods()` and `~methods()`, Tolk has only dot,
one and only way to call a `.method()`.
A method may mutate an object, or may not.
It's a behavioral and semantic difference from FunC.

- `cs.loadInt(32)` modifies a slice and returns an integer
- `b.storeInt(x, 32)` modifies a builder
- `b = b.storeInt()` also works, since it not only modifies, but returns
- chained methods also work, they return `self`
- everything works exactly as expected, similar to JS
- no runtime overhead, exactly same Fift instructions
- custom methods are created with ease
- tilda `~` does not exist in Tolk at all
This commit is contained in:
tolk-vm 2024-10-31 11:18:54 +04:00
parent 12ff28ac94
commit d9dba320cc
No known key found for this signature in database
GPG key ID: 7905DD7FE0324B12
85 changed files with 2710 additions and 1965 deletions

View file

@ -25,12 +25,8 @@ namespace tolk {
*
*/
TmpVar::TmpVar(var_idx_t _idx, bool _is_tmp_unnamed, TypeExpr* _type, SymDef* sym, SrcLocation loc)
: v_type(_type), idx(_idx), is_tmp_unnamed(_is_tmp_unnamed), coord(0), where(loc) {
if (sym) {
name = sym->sym_idx;
sym->value->idx = _idx;
}
TmpVar::TmpVar(var_idx_t _idx, TypeExpr* _type, sym_idx_t sym_idx, SrcLocation loc)
: v_type(_type), idx(_idx), sym_idx(sym_idx), coord(0), where(loc) {
if (!_type) {
v_type = TypeExpr::new_hole();
}
@ -59,8 +55,8 @@ void TmpVar::dump(std::ostream& os) const {
}
void TmpVar::show(std::ostream& os, int omit_idx) const {
if (!is_tmp_unnamed) {
os << G.symbols.get_name(name);
if (!is_unnamed()) {
os << G.symbols.get_name(sym_idx);
if (omit_idx >= 2) {
return;
}
@ -462,16 +458,13 @@ void CodeBlob::print(std::ostream& os, int flags) const {
os << "-------- END ---------\n\n";
}
var_idx_t CodeBlob::create_var(bool is_tmp_unnamed, TypeExpr* var_type, SymDef* sym, SrcLocation location) {
vars.emplace_back(var_cnt, is_tmp_unnamed, var_type, sym, location);
if (sym) {
sym->value->idx = var_cnt;
}
var_idx_t CodeBlob::create_var(TypeExpr* var_type, var_idx_t sym_idx, SrcLocation location) {
vars.emplace_back(var_cnt, var_type, sym_idx, location);
return var_cnt++;
}
bool CodeBlob::import_params(FormalArgList arg_list) {
if (var_cnt || in_var_cnt || op_cnt) {
if (var_cnt || in_var_cnt) {
return false;
}
std::vector<var_idx_t> list;
@ -480,7 +473,7 @@ bool CodeBlob::import_params(FormalArgList arg_list) {
SymDef* arg_sym;
SrcLocation arg_loc;
std::tie(arg_type, arg_sym, arg_loc) = par;
list.push_back(create_var(arg_sym == nullptr, arg_type, arg_sym, arg_loc));
list.push_back(create_var(arg_type, arg_sym ? arg_sym->sym_idx : 0, arg_loc));
}
emplace_back(loc, Op::_Import, list);
in_var_cnt = var_cnt;

View file

@ -46,10 +46,9 @@ int CodeBlob::split_vars(bool strict) {
if (k != 1) {
var.coord = ~((n << 8) + k);
for (int i = 0; i < k; i++) {
auto v = create_var(vars[j].is_tmp_unnamed, comp_types[i], 0, vars[j].where);
auto v = create_var(comp_types[i], vars[j].sym_idx, vars[j].where);
tolk_assert(v == n + i);
tolk_assert(vars[v].idx == v);
vars[v].name = vars[j].name;
vars[v].coord = ((int)j << 8) + i + 1;
}
n += k;

View file

@ -131,7 +131,8 @@ static AnyV maybe_replace_eq_null_with_isNull_call(V<ast_binary_operator> v) {
auto v_ident = createV<ast_identifier>(v->loc, "__isNull"); // built-in function
AnyV v_null = v->get_lhs()->type == ast_null_keyword ? v->get_rhs() : v->get_lhs();
AnyV v_isNull = createV<ast_function_call>(v->loc, v_ident, createV<ast_tensor>(v->loc, {v_null}));
AnyV v_arg = createV<ast_argument>(v->loc, v_null, false);
AnyV v_isNull = createV<ast_function_call>(v->loc, v_ident, createV<ast_argument_list>(v->loc, {v_arg}));
if (v->tok == tok_neq) {
v_isNull = createV<ast_unary_operator>(v->loc, "!", tok_logical_not, v_isNull);
}
@ -165,7 +166,7 @@ static TypeExpr* parse_type1(Lexer& lex, V<ast_genericsT_list> genericsT_list) {
return TypeExpr::new_atomic(TypeExpr::_Builder);
case tok_continuation:
lex.next();
return TypeExpr::new_atomic(TypeExpr::_Cont);
return TypeExpr::new_atomic(TypeExpr::_Continutaion);
case tok_tuple:
lex.next();
return TypeExpr::new_atomic(TypeExpr::_Tuple);
@ -177,6 +178,8 @@ static TypeExpr* parse_type1(Lexer& lex, V<ast_genericsT_list> genericsT_list) {
return TypeExpr::new_tensor({});
case tok_bool:
lex.error("bool type is not supported yet");
case tok_self:
lex.error("`self` type can be used only as a return type of a function (enforcing it to be chainable)");
case tok_identifier:
if (int idx = genericsT_list ? genericsT_list->lookup_idx(lex.cur_str()) : -1; idx != -1) {
lex.next();
@ -229,13 +232,27 @@ static TypeExpr* parse_type(Lexer& lex, V<ast_genericsT_list> genericsT_list) {
AnyV parse_expr(Lexer& lex);
static AnyV parse_parameter(Lexer& lex, V<ast_genericsT_list> genericsT_list) {
static AnyV parse_parameter(Lexer& lex, V<ast_genericsT_list> genericsT_list, bool is_first) {
SrcLocation loc = lex.cur_location();
// argument name (or underscore for an unnamed parameter)
// optional keyword `mutate` meaning that a function will mutate a passed argument (like passed by reference)
bool declared_as_mutate = false;
bool is_param_self = false;
if (lex.tok() == tok_mutate) {
lex.next();
declared_as_mutate = true;
}
// parameter name (or underscore for an unnamed parameter)
std::string_view param_name;
if (lex.tok() == tok_identifier) {
param_name = lex.cur_str();
} else if (lex.tok() == tok_self) {
if (!is_first) {
lex.error("`self` can only be the first parameter");
}
param_name = "self";
is_param_self = true;
} else if (lex.tok() != tok_underscore) {
lex.unexpected("parameter name");
}
@ -245,8 +262,14 @@ static AnyV parse_parameter(Lexer& lex, V<ast_genericsT_list> genericsT_list) {
// parameter type after colon, also mandatory (even explicit ":auto")
lex.expect(tok_colon, "`: <parameter_type>`");
TypeExpr* param_type = parse_type(lex, genericsT_list);
if (declared_as_mutate && !param_type->has_fixed_width()) {
throw ParseError(loc, "`mutate` parameter must be strictly typed");
}
if (is_param_self && !param_type->has_fixed_width()) {
throw ParseError(loc, "`self` parameter must be strictly typed");
}
return createV<ast_parameter>(loc, v_ident, param_type);
return createV<ast_parameter>(loc, v_ident, param_type, declared_as_mutate);
}
static AnyV parse_global_var_declaration(Lexer& lex, const std::vector<V<ast_annotation>>& annotations) {
@ -301,21 +324,52 @@ static AnyV parse_constant_declaration(Lexer& lex, const std::vector<V<ast_annot
return createV<ast_constant_declaration>(loc, v_ident, declared_type, init_value);
}
static AnyV parse_parameter_list(Lexer& lex, V<ast_genericsT_list> genericsT_list) {
// "parameters" are at function declaration: `fun f(param1: int, mutate param2: slice)`
static V<ast_parameter_list> parse_parameter_list(Lexer& lex, V<ast_genericsT_list> genericsT_list) {
SrcLocation loc = lex.cur_location();
std::vector<AnyV> params;
lex.expect(tok_oppar, "parameter list");
if (lex.tok() != tok_clpar) {
params.push_back(parse_parameter(lex, genericsT_list));
params.push_back(parse_parameter(lex, genericsT_list, true));
while (lex.tok() == tok_comma) {
lex.next();
params.push_back(parse_parameter(lex, genericsT_list));
params.push_back(parse_parameter(lex, genericsT_list, false));
}
}
lex.expect(tok_clpar, "`)`");
return createV<ast_parameter_list>(loc, std::move(params));
}
// "arguments" are at function call: `f(arg1, mutate arg2)`
static AnyV parse_argument(Lexer& lex) {
SrcLocation loc = lex.cur_location();
// keyword `mutate` is necessary when a parameter is declared `mutate` (to make mutation obvious for the reader)
bool passed_as_mutate = false;
if (lex.tok() == tok_mutate) {
lex.next();
passed_as_mutate = true;
}
AnyV expr = parse_expr(lex);
return createV<ast_argument>(loc, expr, passed_as_mutate);
}
static V<ast_argument_list> parse_argument_list(Lexer& lex) {
SrcLocation loc = lex.cur_location();
std::vector<AnyV> args;
lex.expect(tok_oppar, "`(`");
if (lex.tok() != tok_clpar) {
args.push_back(parse_argument(lex));
while (lex.tok() == tok_comma) {
lex.next();
args.push_back(parse_argument(lex));
}
}
lex.expect(tok_clpar, "`)`");
return createV<ast_argument_list>(loc, std::move(args));
}
// parse (expr) / [expr] / identifier / number
static AnyV parse_expr100(Lexer& lex) {
SrcLocation loc = lex.cur_location();
@ -384,6 +438,10 @@ static AnyV parse_expr100(Lexer& lex) {
lex.next();
return createV<ast_null_keyword>(loc);
}
case tok_self: {
lex.next();
return createV<ast_self_keyword>(loc);
}
case tok_identifier: {
std::string_view str_val = lex.cur_str();
lex.next();
@ -400,48 +458,25 @@ static AnyV parse_expr100(Lexer& lex) {
}
}
// parse E(expr)
// parse E(args)
static AnyV parse_expr90(Lexer& lex) {
AnyV res = parse_expr100(lex);
if (lex.tok() == tok_oppar) {
lex.next();
SrcLocation loc = lex.cur_location();
std::vector<AnyV> args;
if (lex.tok() != tok_clpar) {
args.push_back(parse_expr(lex));
while (lex.tok() == tok_comma) {
lex.next();
args.push_back(parse_expr(lex));
}
}
lex.expect(tok_clpar, "`)`");
return createV<ast_function_call>(res->loc, res, createV<ast_tensor>(loc, std::move(args)));
return createV<ast_function_call>(res->loc, res, parse_argument_list(lex));
}
return res;
}
// parse E .method ~method E (left-to-right)
// parse E.method(...) (left-to-right)
static AnyV parse_expr80(Lexer& lex) {
AnyV lhs = parse_expr90(lex);
while (lex.tok() == tok_identifier && (lex.cur_str()[0] == '.' || lex.cur_str()[0] == '~')) {
while (lex.tok() == tok_dot) {
SrcLocation loc = lex.cur_location();
lex.next();
lex.check(tok_identifier, "method name");
std::string_view method_name = lex.cur_str();
lex.next();
SrcLocation loc = lex.cur_location();
std::vector<AnyV> args;
lex.expect(tok_oppar, "`(`");
if (lex.tok() != tok_clpar) {
args.push_back(parse_expr(lex));
while (lex.tok() == tok_comma) {
lex.next();
args.push_back(parse_expr(lex));
}
}
lex.expect(tok_clpar, "`)`");
lhs = createV<ast_dot_tilde_call>(lhs->loc, method_name, lhs, createV<ast_tensor>(loc, std::move(args)));
lhs = createV<ast_dot_method_call>(loc, method_name, lhs, parse_argument_list(lex));
}
return lhs;
}
@ -586,11 +621,11 @@ AnyV parse_expr(Lexer& lex) {
AnyV parse_statement(Lexer& lex);
static AnyV parse_var_declaration_lhs(Lexer& lex) {
static AnyV parse_var_declaration_lhs(Lexer& lex, bool is_immutable) {
SrcLocation loc = lex.cur_location();
if (lex.tok() == tok_oppar) {
lex.next();
AnyV first = parse_var_declaration_lhs(lex);
AnyV first = parse_var_declaration_lhs(lex, is_immutable);
if (lex.tok() == tok_clpar) {
lex.next();
return createV<ast_parenthesized_expr>(loc, first);
@ -598,17 +633,17 @@ static AnyV parse_var_declaration_lhs(Lexer& lex) {
std::vector<AnyV> args(1, first);
while (lex.tok() == tok_comma) {
lex.next();
args.push_back(parse_var_declaration_lhs(lex));
args.push_back(parse_var_declaration_lhs(lex, is_immutable));
}
lex.expect(tok_clpar, "`)`");
return createV<ast_tensor>(loc, std::move(args));
}
if (lex.tok() == tok_opbracket) {
lex.next();
std::vector<AnyV> args(1, parse_var_declaration_lhs(lex));
std::vector<AnyV> args(1, parse_var_declaration_lhs(lex, is_immutable));
while (lex.tok() == tok_comma) {
lex.next();
args.push_back(parse_var_declaration_lhs(lex));
args.push_back(parse_var_declaration_lhs(lex, is_immutable));
}
lex.expect(tok_clbracket, "`]`");
return createV<ast_tensor_square>(loc, std::move(args));
@ -625,7 +660,7 @@ static AnyV parse_var_declaration_lhs(Lexer& lex) {
lex.next();
marked_as_redef = true;
}
return createV<ast_local_var>(loc, v_ident, declared_type, marked_as_redef);
return createV<ast_local_var>(loc, v_ident, declared_type, is_immutable, marked_as_redef);
}
if (lex.tok() == tok_underscore) {
TypeExpr* declared_type = nullptr;
@ -634,21 +669,17 @@ static AnyV parse_var_declaration_lhs(Lexer& lex) {
lex.next();
declared_type = parse_type(lex, nullptr);
}
return createV<ast_local_var>(loc, createV<ast_underscore>(loc), declared_type, false);
return createV<ast_local_var>(loc, createV<ast_underscore>(loc), declared_type, true, false);
}
lex.unexpected("variable name");
}
static AnyV parse_local_vars_declaration(Lexer& lex) {
SrcLocation loc = lex.cur_location();
bool immutable = lex.tok() == tok_val;
bool is_immutable = lex.tok() == tok_val;
lex.next();
if (immutable) {
lex.error("immutable variables are not supported yet");
}
AnyV lhs = parse_var_declaration_lhs(lex);
AnyV lhs = parse_var_declaration_lhs(lex, is_immutable);
if (lex.tok() != tok_assign) {
lex.error("variables declaration must be followed by assignment: `var xxx = ...`");
}
@ -885,10 +916,10 @@ static AnyV parse_asm_func_body(Lexer& lex, V<ast_parameter_list> param_list) {
std::vector<int> arg_order, ret_order;
if (lex.tok() == tok_oppar) {
lex.next();
while (lex.tok() == tok_identifier || lex.tok() == tok_int_const) {
while (lex.tok() == tok_identifier || lex.tok() == tok_self) {
int arg_idx = param_list->lookup_idx(lex.cur_str());
if (arg_idx == -1) {
lex.unexpected("argument name");
lex.unexpected("parameter name");
}
arg_order.push_back(arg_idx);
lex.next();
@ -1006,17 +1037,44 @@ static AnyV parse_function_declaration(Lexer& lex, const std::vector<V<ast_annot
genericsT_list = parse_genericsT_list(lex)->as<ast_genericsT_list>();
}
V<ast_parameter_list> param_list = parse_parameter_list(lex, genericsT_list)->as<ast_parameter_list>();
V<ast_parameter_list> v_param_list = parse_parameter_list(lex, genericsT_list)->as<ast_parameter_list>();
bool accepts_self = !v_param_list->empty() && v_param_list->get_param(0)->get_identifier()->name == "self";
int n_mutate_params = v_param_list->get_mutate_params_count();
TypeExpr* ret_type = nullptr;
bool returns_self = false;
if (lex.tok() == tok_colon) { // : <ret_type> (if absent, it means "auto infer", not void)
lex.next();
ret_type = parse_type(lex, genericsT_list);
if (lex.tok() == tok_self) {
if (!accepts_self) {
lex.error("only a member function can return `self` (which accepts `self` first parameter)");
}
lex.next();
returns_self = true;
ret_type = TypeExpr::new_unit();
} else {
ret_type = parse_type(lex, genericsT_list);
}
}
if (is_entrypoint && (is_get_method || genericsT_list || !annotations.empty())) {
if (is_entrypoint && (is_get_method || genericsT_list || n_mutate_params || accepts_self)) {
throw ParseError(loc, "invalid declaration of a reserved function");
}
if (is_get_method && (genericsT_list || n_mutate_params || accepts_self)) {
throw ParseError(loc, "get methods can't have `mutate` and `self` params");
}
if (n_mutate_params) {
std::vector<TypeExpr*> ret_tensor_items;
ret_tensor_items.reserve(1 + n_mutate_params);
for (AnyV v_param : v_param_list->get_params()) {
if (v_param->as<ast_parameter>()->declared_as_mutate) {
ret_tensor_items.emplace_back(v_param->as<ast_parameter>()->param_type);
}
}
ret_tensor_items.emplace_back(ret_type ? ret_type : TypeExpr::new_hole());
ret_type = TypeExpr::new_tensor(std::move(ret_tensor_items));
}
AnyV v_body = nullptr;
@ -1030,17 +1088,19 @@ static AnyV parse_function_declaration(Lexer& lex, const std::vector<V<ast_annot
if (!ret_type) {
lex.error("asm function must specify return type");
}
v_body = parse_asm_func_body(lex, param_list);
v_body = parse_asm_func_body(lex, v_param_list);
} else {
lex.unexpected("{ function body }");
}
auto f_declaration = createV<ast_function_declaration>(loc, v_ident, param_list, v_body);
auto f_declaration = createV<ast_function_declaration>(loc, v_ident, v_param_list, v_body);
f_declaration->ret_type = ret_type ? ret_type : TypeExpr::new_hole();
f_declaration->is_entrypoint = is_entrypoint;
f_declaration->genericsT_list = genericsT_list;
f_declaration->marked_as_get_method = is_get_method;
f_declaration->marked_as_builtin = v_body->type == ast_empty;
f_declaration->accepts_self = accepts_self;
f_declaration->returns_self = returns_self;
for (auto v_annotation : annotations) {
switch (v_annotation->kind) {
@ -1054,7 +1114,7 @@ static AnyV parse_function_declaration(Lexer& lex, const std::vector<V<ast_annot
f_declaration->marked_as_pure = true;
break;
case AnnotationKind::method_id:
if (is_get_method || genericsT_list || is_entrypoint) {
if (is_get_method || genericsT_list || is_entrypoint || n_mutate_params || accepts_self) {
v_annotation->error("@method_id can be specified only for regular functions");
}
f_declaration->method_id = v_annotation->get_arg()->get_item(0)->as<ast_int_const>();

View file

@ -80,8 +80,8 @@ protected:
virtual AnyV replace(V<ast_bool_const> v) { return replace_children(v); }
virtual AnyV replace(V<ast_null_keyword> v) { return replace_children(v); }
virtual AnyV replace(V<ast_function_call> v) { return replace_children(v); }
virtual AnyV replace(V<ast_dot_method_call> v) { return replace_children(v); }
virtual AnyV replace(V<ast_underscore> v) { return replace_children(v); }
virtual AnyV replace(V<ast_dot_tilde_call> v) { return replace_children(v); }
virtual AnyV replace(V<ast_unary_operator> v) { return replace_children(v); }
virtual AnyV replace(V<ast_binary_operator> v) { return replace_children(v); }
virtual AnyV replace(V<ast_ternary_operator> v) { return replace_children(v); }
@ -109,9 +109,10 @@ protected:
case ast_string_const: return replace(v->as<ast_string_const>());
case ast_bool_const: return replace(v->as<ast_bool_const>());
case ast_null_keyword: return replace(v->as<ast_null_keyword>());
case ast_self_keyword: return replace(v->as<ast_self_keyword>());
case ast_function_call: return replace(v->as<ast_function_call>());
case ast_dot_method_call: return replace(v->as<ast_dot_method_call>());
case ast_underscore: return replace(v->as<ast_underscore>());
case ast_dot_tilde_call: return replace(v->as<ast_dot_tilde_call>());
case ast_unary_operator: return replace(v->as<ast_unary_operator>());
case ast_binary_operator: return replace(v->as<ast_binary_operator>());
case ast_ternary_operator: return replace(v->as<ast_ternary_operator>());

View file

@ -40,11 +40,14 @@ class ASTStringifier final : public ASTVisitor {
{ast_string_const, "ast_string_const"},
{ast_bool_const, "ast_bool_const"},
{ast_null_keyword, "ast_null_keyword"},
{ast_self_keyword, "ast_self_keyword"},
{ast_argument, "ast_argument"},
{ast_argument_list, "ast_argument_list"},
{ast_function_call, "ast_function_call"},
{ast_dot_method_call, "ast_dot_method_call"},
{ast_global_var_declaration, "ast_global_var_declaration"},
{ast_constant_declaration, "ast_constant_declaration"},
{ast_underscore, "ast_underscore"},
{ast_dot_tilde_call, "ast_dot_tilde_call"},
{ast_unary_operator, "ast_unary_operator"},
{ast_binary_operator, "ast_binary_operator"},
{ast_ternary_operator, "ast_ternary_operator"},
@ -125,12 +128,12 @@ class ASTStringifier final : public ASTVisitor {
}
return {};
}
case ast_dot_method_call:
return static_cast<std::string>(v->as<ast_dot_method_call>()->method_name);
case ast_global_var_declaration:
return static_cast<std::string>(v->as<ast_global_var_declaration>()->get_identifier()->name);
case ast_constant_declaration:
return static_cast<std::string>(v->as<ast_constant_declaration>()->get_identifier()->name);
case ast_dot_tilde_call:
return static_cast<std::string>(v->as<ast_dot_tilde_call>()->method_name);
case ast_unary_operator:
return static_cast<std::string>(v->as<ast_unary_operator>()->operator_name);
case ast_binary_operator:
@ -208,11 +211,14 @@ public:
case ast_string_const: return handle_vertex(v->as<ast_string_const>());
case ast_bool_const: return handle_vertex(v->as<ast_bool_const>());
case ast_null_keyword: return handle_vertex(v->as<ast_null_keyword>());
case ast_self_keyword: return handle_vertex(v->as<ast_self_keyword>());
case ast_argument: return handle_vertex(v->as<ast_argument>());
case ast_argument_list: return handle_vertex(v->as<ast_argument_list>());
case ast_function_call: return handle_vertex(v->as<ast_function_call>());
case ast_dot_method_call: return handle_vertex(v->as<ast_dot_method_call>());
case ast_global_var_declaration: return handle_vertex(v->as<ast_global_var_declaration>());
case ast_constant_declaration: return handle_vertex(v->as<ast_constant_declaration>());
case ast_underscore: return handle_vertex(v->as<ast_underscore>());
case ast_dot_tilde_call: return handle_vertex(v->as<ast_dot_tilde_call>());
case ast_unary_operator: return handle_vertex(v->as<ast_unary_operator>());
case ast_binary_operator: return handle_vertex(v->as<ast_binary_operator>());
case ast_ternary_operator: return handle_vertex(v->as<ast_ternary_operator>());

View file

@ -75,9 +75,10 @@ protected:
virtual void visit(V<ast_string_const> v) { return visit_children(v); }
virtual void visit(V<ast_bool_const> v) { return visit_children(v); }
virtual void visit(V<ast_null_keyword> v) { return visit_children(v); }
virtual void visit(V<ast_self_keyword> v) { return visit_children(v); }
virtual void visit(V<ast_function_call> v) { return visit_children(v); }
virtual void visit(V<ast_dot_method_call> v) { return visit_children(v); }
virtual void visit(V<ast_underscore> v) { return visit_children(v); }
virtual void visit(V<ast_dot_tilde_call> v) { return visit_children(v); }
virtual void visit(V<ast_unary_operator> v) { return visit_children(v); }
virtual void visit(V<ast_binary_operator> v) { return visit_children(v); }
virtual void visit(V<ast_ternary_operator> v) { return visit_children(v); }
@ -103,9 +104,10 @@ protected:
case ast_string_const: return visit(v->as<ast_string_const>());
case ast_bool_const: return visit(v->as<ast_bool_const>());
case ast_null_keyword: return visit(v->as<ast_null_keyword>());
case ast_self_keyword: return visit(v->as<ast_self_keyword>());
case ast_function_call: return visit(v->as<ast_function_call>());
case ast_dot_method_call: return visit(v->as<ast_dot_method_call>());
case ast_underscore: return visit(v->as<ast_underscore>());
case ast_dot_tilde_call: return visit(v->as<ast_dot_tilde_call>());
case ast_unary_operator: return visit(v->as<ast_unary_operator>());
case ast_binary_operator: return visit(v->as<ast_binary_operator>());
case ast_ternary_operator: return visit(v->as<ast_ternary_operator>());

View file

@ -86,6 +86,16 @@ int Vertex<ast_parameter_list>::lookup_idx(std::string_view param_name) const {
return -1;
}
int Vertex<ast_parameter_list>::get_mutate_params_count() const {
int n = 0;
for (AnyV param : children) {
if (param->as<ast_parameter>()->declared_as_mutate) {
n++;
}
}
return n;
}
void Vertex<ast_import_statement>::mutate_set_src_file(const SrcFile* file) const {
const_cast<Vertex*>(this)->file = file;
}

View file

@ -68,11 +68,14 @@ enum ASTNodeType {
ast_string_const,
ast_bool_const,
ast_null_keyword,
ast_self_keyword,
ast_argument,
ast_argument_list,
ast_function_call,
ast_dot_method_call,
ast_global_var_declaration,
ast_constant_declaration,
ast_underscore,
ast_dot_tilde_call,
ast_unary_operator,
ast_binary_operator,
ast_ternary_operator,
@ -285,13 +288,50 @@ struct Vertex<ast_null_keyword> final : ASTNodeLeaf {
};
template<>
struct Vertex<ast_function_call> final : ASTNodeBinary {
// even for f(1,2,3), f (lhs) is called with a single arg (tensor "(1,2,3)") (rhs)
AnyV get_called_f() const { return lhs; }
auto get_called_arg() const { return rhs->as<ast_tensor>(); }
struct Vertex<ast_self_keyword> final : ASTNodeLeaf {
explicit Vertex(SrcLocation loc)
: ASTNodeLeaf(ast_self_keyword, loc) {}
};
Vertex(SrcLocation loc, AnyV lhs_f, V<ast_tensor> arg)
: ASTNodeBinary(ast_function_call, loc, lhs_f, arg) {}
template<>
struct Vertex<ast_argument> final : ASTNodeUnary {
bool passed_as_mutate; // when called `f(mutate arg)`, not `f(arg)`
AnyV get_expr() const { return child; }
explicit Vertex(SrcLocation loc, AnyV expr, bool passed_as_mutate)
: ASTNodeUnary(ast_argument, loc, expr), passed_as_mutate(passed_as_mutate) {}
};
template<>
struct Vertex<ast_argument_list> final : ASTNodeVararg {
const std::vector<AnyV>& get_arguments() const { return children; }
auto get_arg(int i) const { return children.at(i)->as<ast_argument>(); }
explicit Vertex(SrcLocation loc, std::vector<AnyV> arguments)
: ASTNodeVararg(ast_argument_list, loc, std::move(arguments)) {}
};
template<>
struct Vertex<ast_function_call> final : ASTNodeBinary {
AnyV get_called_f() const { return lhs; }
auto get_arg_list() const { return rhs->as<ast_argument_list>(); }
int get_num_args() const { return rhs->as<ast_argument_list>()->size(); }
auto get_arg(int i) const { return rhs->as<ast_argument_list>()->get_arg(i); }
Vertex(SrcLocation loc, AnyV lhs_f, V<ast_argument_list> arguments)
: ASTNodeBinary(ast_function_call, loc, lhs_f, arguments) {}
};
template<>
struct Vertex<ast_dot_method_call> final : ASTNodeBinary {
std::string_view method_name;
AnyV get_obj() const { return lhs; }
auto get_arg_list() const { return rhs->as<ast_argument_list>(); }
Vertex(SrcLocation loc, std::string_view method_name, AnyV lhs, V<ast_argument_list> arguments)
: ASTNodeBinary(ast_dot_method_call, loc, lhs, arguments), method_name(method_name) {}
};
template<>
@ -321,17 +361,6 @@ struct Vertex<ast_underscore> final : ASTNodeLeaf {
: ASTNodeLeaf(ast_underscore, loc) {}
};
template<>
struct Vertex<ast_dot_tilde_call> final : ASTNodeBinary {
std::string_view method_name; // starts with . or ~
AnyV get_lhs() const { return lhs; }
auto get_arg() const { return rhs->as<ast_tensor>(); }
Vertex(SrcLocation loc, std::string_view method_name, AnyV lhs, V<ast_tensor> arg)
: ASTNodeBinary(ast_dot_tilde_call, loc, lhs, arg), method_name(method_name) {}
};
template<>
struct Vertex<ast_unary_operator> final : ASTNodeUnary {
std::string_view operator_name;
@ -475,11 +504,13 @@ struct Vertex<ast_genericsT_list> final : ASTNodeVararg {
template<>
struct Vertex<ast_parameter> final : ASTNodeUnary {
TypeExpr* param_type;
bool declared_as_mutate; // declared as `mutate param_name`
auto get_identifier() const { return child->as<ast_identifier>(); } // for underscore, its str_val is empty
auto get_identifier() const { return child->as<ast_identifier>(); } // for underscore, name is empty
bool is_underscore() const { return child->as<ast_identifier>()->name.empty(); }
Vertex(SrcLocation loc, V<ast_identifier> name_identifier, TypeExpr* param_type)
: ASTNodeUnary(ast_parameter, loc, name_identifier), param_type(param_type) {}
Vertex(SrcLocation loc, V<ast_identifier> name_identifier, TypeExpr* param_type, bool declared_as_mutate)
: ASTNodeUnary(ast_parameter, loc, name_identifier), param_type(param_type), declared_as_mutate(declared_as_mutate) {}
};
template<>
@ -491,6 +522,8 @@ struct Vertex<ast_parameter_list> final : ASTNodeVararg {
: ASTNodeVararg(ast_parameter_list, loc, std::move(params)) {}
int lookup_idx(std::string_view param_name) const;
int get_mutate_params_count() const;
bool has_mutate_params() const { return get_mutate_params_count() > 0; }
};
template<>
@ -519,12 +552,13 @@ struct Vertex<ast_annotation> final : ASTNodeUnary {
template<>
struct Vertex<ast_local_var> final : ASTNodeUnary {
TypeExpr* declared_type;
bool is_immutable; // declared via 'val', not 'var'
bool marked_as_redef; // var (existing_var redef, new_var: int) = ...
AnyV get_identifier() const { return child; } // ast_identifier / ast_underscore
Vertex(SrcLocation loc, AnyV name_identifier, TypeExpr* declared_type, bool marked_as_redef)
: ASTNodeUnary(ast_local_var, loc, name_identifier), declared_type(declared_type), marked_as_redef(marked_as_redef) {}
Vertex(SrcLocation loc, AnyV name_identifier, TypeExpr* declared_type, bool is_immutable, bool marked_as_redef)
: ASTNodeUnary(ast_local_var, loc, name_identifier), declared_type(declared_type), is_immutable(is_immutable), marked_as_redef(marked_as_redef) {}
};
template<>
@ -552,6 +586,8 @@ struct Vertex<ast_function_declaration> final : ASTNodeVararg {
bool marked_as_get_method = false;
bool marked_as_inline = false;
bool marked_as_inline_ref = false;
bool accepts_self = false;
bool returns_self = false;
V<ast_int_const> method_id = nullptr;
bool is_asm_function() const { return children.at(2)->type == ast_asm_body; }

View file

@ -29,44 +29,60 @@ using namespace std::literals::string_literals;
SymDef* define_builtin_func_impl(const std::string& name, SymValAsmFunc* func_val) {
sym_idx_t name_idx = G.symbols.lookup_add(name);
SymDef* def = define_global_symbol(name_idx);
if (!def) {
std::cerr << "fatal: global function `" << name << "` already defined" << std::endl;
std::exit(1);
}
func_val->flags |= SymValFunc::flagBuiltinFunction;
tolk_assert(!def->value);
def->value = func_val;
#ifdef TOLK_DEBUG
dynamic_cast<SymValAsmFunc*>(def->value)->name = name;
def->value->sym_name = name;
#endif
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});
// given func_type = `(slice, int) -> slice` and func flags, create SymDef for parameters
// currently (see at the bottom) parameters of built-in functions are unnamed:
// built-in functions are created using a resulting type
static std::vector<SymDef*> define_builtin_parameters(const TypeExpr* func_type, int func_flags) {
// `loadInt()`, `storeInt()`: they accept `self` and mutate it; no other options available in built-ins for now
bool is_mutate_self = func_flags & SymValFunc::flagHasMutateParams;
// func_type a map (params_type -> ret_type), probably surrounded by forall (internal representation of <T>)
TypeExpr* params_type = func_type->constr == TypeExpr::te_ForAll ? func_type->args[0]->args[0] : func_type->args[0];
std::vector<SymDef*> parameters;
if (params_type->constr == TypeExpr::te_Tensor) { // multiple parameters: it's a tensor
parameters.reserve(params_type->args.size());
for (int i = 0; i < static_cast<int>(params_type->args.size()); ++i) {
SymDef* sym_def = define_parameter(i, {});
SymValVariable* sym_val = new SymValVariable(i, params_type->args[i]);
if (i == 0 && is_mutate_self) {
sym_val->flags |= SymValVariable::flagMutateParameter;
}
sym_def->value = sym_val;
parameters.emplace_back(sym_def);
}
} else { // single parameter
SymDef* sym_def = define_parameter(0, {});
SymValVariable* sym_val = new SymValVariable(0, params_type);
if (is_mutate_self) {
sym_val->flags |= SymValVariable::flagMutateParameter;
}
sym_def->value = sym_val;
parameters.emplace_back(sym_def);
}
return parameters;
}
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});
static SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const simple_compile_func_t& func, int flags) {
return define_builtin_func_impl(name, new SymValAsmFunc(define_builtin_parameters(func_type, flags), func_type, func, flags | SymValFunc::flagBuiltinFunction));
}
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});
static SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const AsmOp& macro, int flags) {
return define_builtin_func_impl(name, new SymValAsmFunc(define_builtin_parameters(func_type, flags), func_type, make_simple_compile(macro), flags | SymValFunc::flagBuiltinFunction));
}
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) {
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 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) {
return define_builtin_func_impl(name, new SymValAsmFunc{func_type, make_simple_compile(macro), arg_order, ret_order, !impure});
static SymDef* define_builtin_func(const std::string& name, TypeExpr* func_type, const simple_compile_func_t& func, int flags,
std::initializer_list<int> arg_order, std::initializer_list<int> ret_order) {
return define_builtin_func_impl(name, new SymValAsmFunc(define_builtin_parameters(func_type, flags), func_type, func, flags | SymValFunc::flagBuiltinFunction, arg_order, ret_order));
}
bool SymValAsmFunc::compile(AsmOpList& dest, std::vector<VarDescr>& out, std::vector<VarDescr>& in,
@ -963,7 +979,7 @@ AsmOp compile_throw(std::vector<VarDescr>& res, std::vector<VarDescr>& args, Src
}
}
AsmOp compile_throw_if_unless(std::vector<VarDescr>& res, std::vector<VarDescr>& args) {
AsmOp compile_throw_if_unless(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation) {
tolk_assert(res.empty() && args.size() == 3);
VarDescr &x = args[0], &y = args[1], &z = args[2];
if (!z.always_true() && !z.always_false()) {
@ -1007,10 +1023,10 @@ AsmOp compile_bool_const(std::vector<VarDescr>& res, std::vector<VarDescr>& args
return AsmOp::Const(val ? "TRUE" : "FALSE");
}
// (slice, int) load_int(slice s, int len) asm(s len -> 1 0) "LDIX";
// (slice, int) load_uint(slice s, int len) asm( -> 1 0) "LDUX";
// int preload_int(slice s, int len) asm "PLDIX";
// int preload_uint(slice s, int len) asm "PLDUX";
// fun loadInt (mutate s: slice, len: int): int asm(s len -> 1 0) "LDIX";
// fun loadUint (mutate s: slice, len: int): int asm( -> 1 0) "LDUX";
// fun preloadInt (s: slice, len: int): int asm "PLDIX";
// fun preloadUint(s: slice, len: int): int asm "PLDUX";
AsmOp compile_fetch_int(std::vector<VarDescr>& res, std::vector<VarDescr>& args, bool fetch, bool sgnd) {
tolk_assert(args.size() == 2 && res.size() == 1 + (unsigned)fetch);
auto &y = args[1], &r = res.back();
@ -1032,8 +1048,8 @@ AsmOp compile_fetch_int(std::vector<VarDescr>& res, std::vector<VarDescr>& args,
return exec_op((fetch ? "LD"s : "PLD"s) + (sgnd ? "IX" : "UX"), 2, 1 + (unsigned)fetch);
}
// builder store_uint(builder b, int x, int len) asm(x b len) "STUX";
// builder store_int(builder b, int x, int len) asm(x b len) "STIX";
// fun storeInt (mutate self: builder, x: int, len: int): self asm(x b len) "STIX";
// fun storeUint (mutate self: builder, x: int, len: int): self asm(x b len) "STUX";
AsmOp compile_store_int(std::vector<VarDescr>& res, std::vector<VarDescr>& args, bool sgnd) {
tolk_assert(args.size() == 3 && res.size() == 1);
auto& z = args[2];
@ -1044,6 +1060,8 @@ AsmOp compile_store_int(std::vector<VarDescr>& res, std::vector<VarDescr>& args,
return exec_op("ST"s + (sgnd ? "IX" : "UX"), 3, 1);
}
// fun loadBits (mutate self: slice, len: int): self asm(s len -> 1 0) "LDSLICEX"
// fun preloadBits(self: slice, len: int): slice asm(s len -> 1 0) "PLDSLICEX"
AsmOp compile_fetch_slice(std::vector<VarDescr>& res, std::vector<VarDescr>& args, bool fetch) {
tolk_assert(args.size() == 2 && res.size() == 1 + (unsigned)fetch);
auto& y = args[1];
@ -1058,7 +1076,7 @@ AsmOp compile_fetch_slice(std::vector<VarDescr>& res, std::vector<VarDescr>& arg
return exec_op(fetch ? "LDSLICEX" : "PLDSLICEX", 2, 1 + (unsigned)fetch);
}
// <type> <type>_at(tuple t, int index) asm "INDEXVAR";
// fun at<X>(t: tuple, index: int): X asm "INDEXVAR";
AsmOp compile_tuple_at(std::vector<VarDescr>& res, std::vector<VarDescr>& args, SrcLocation) {
tolk_assert(args.size() == 2 && res.size() == 1);
auto& y = args[1];
@ -1079,102 +1097,146 @@ AsmOp compile_is_null(std::vector<VarDescr>& res, std::vector<VarDescr>& args, S
void define_builtins() {
using namespace std::placeholders;
auto Unit = TypeExpr::new_unit();
auto Int = TypeExpr::new_atomic(TypeExpr::_Int);
auto Cell = TypeExpr::new_atomic(TypeExpr::_Cell);
auto Slice = TypeExpr::new_atomic(TypeExpr::_Slice);
auto Builder = TypeExpr::new_atomic(TypeExpr::_Builder);
// auto Null = TypeExpr::new_atomic(TypeExpr::_Null);
auto Tuple = TypeExpr::new_atomic(TypeExpr::_Tuple);
auto Int2 = TypeExpr::new_tensor({Int, Int});
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(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);
auto impure_un_op = TypeExpr::new_map(Int, Unit);
auto fetch_int_op = TypeExpr::new_map(SliceInt, SliceInt);
auto prefetch_int_op = TypeExpr::new_map(SliceInt, Int);
auto store_int_op = TypeExpr::new_map(TypeExpr::new_tensor({Builder, Int, Int}), Builder);
auto store_int_method =
TypeExpr::new_map(TypeExpr::new_tensor({Builder, Int, Int}), TypeExpr::new_tensor({Builder, Unit}));
auto fetch_slice_op = TypeExpr::new_map(SliceInt, TypeExpr::new_tensor({Slice, Slice}));
auto prefetch_slice_op = TypeExpr::new_map(SliceInt, Slice);
//auto arith_null_op = TypeExpr::new_map(TypeExpr::new_unit(), Int);
auto throw_arg_op = TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_tensor({X, Int}), Unit));
// prevent unused vars warnings (there vars are created to acquire initial id of TypeExpr::value)
static_cast<void>(Z);
static_cast<void>(XY);
static_cast<void>(Cell);
TypeExpr* Unit = TypeExpr::new_unit();
TypeExpr* Int = TypeExpr::new_atomic(TypeExpr::_Int);
TypeExpr* Slice = TypeExpr::new_atomic(TypeExpr::_Slice);
TypeExpr* Builder = TypeExpr::new_atomic(TypeExpr::_Builder);
TypeExpr* Tuple = TypeExpr::new_atomic(TypeExpr::_Tuple);
TypeExpr* Int2 = TypeExpr::new_tensor({Int, Int});
TypeExpr* Int3 = TypeExpr::new_tensor({Int, Int, Int});
TypeExpr* TupleInt = TypeExpr::new_tensor({Tuple, Int});
TypeExpr* SliceInt = TypeExpr::new_tensor({Slice, Int});
TypeExpr* X = TypeExpr::new_var(0);
TypeExpr* arith_bin_op = TypeExpr::new_map(Int2, Int);
TypeExpr* arith_un_op = TypeExpr::new_map(Int, Int);
TypeExpr* impure_un_op = TypeExpr::new_map(Int, Unit);
TypeExpr* fetch_int_op_mutate = TypeExpr::new_map(SliceInt, SliceInt);
TypeExpr* prefetch_int_op = TypeExpr::new_map(SliceInt, Int);
TypeExpr* store_int_mutate = TypeExpr::new_map(TypeExpr::new_tensor({Builder, Int, Int}), TypeExpr::new_tensor({Builder, Unit}));
TypeExpr* fetch_slice_op_mutate = TypeExpr::new_map(SliceInt, TypeExpr::new_tensor({Slice, Slice}));
TypeExpr* prefetch_slice_op = TypeExpr::new_map(SliceInt, Slice);
TypeExpr* throw_arg_op = TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_tensor({X, Int}), Unit));
define_builtin_func("_+_", arith_bin_op, compile_add);
define_builtin_func("_-_", arith_bin_op, compile_sub);
define_builtin_func("-_", arith_un_op, compile_unary_minus);
define_builtin_func("+_", arith_un_op, compile_unary_plus);
define_builtin_func("_*_", arith_bin_op, compile_mul);
define_builtin_func("_/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, -1));
define_builtin_func("_~/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 0));
define_builtin_func("_^/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 1));
define_builtin_func("_%_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, -1));
define_builtin_func("_<<_", arith_bin_op, compile_lshift);
define_builtin_func("_>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, -1));
define_builtin_func("_~>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 0));
define_builtin_func("_^>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 1));
define_builtin_func("!_", arith_un_op, compile_logical_not);
define_builtin_func("~_", arith_un_op, compile_bitwise_not);
define_builtin_func("_&_", arith_bin_op, compile_bitwise_and);
define_builtin_func("_|_", arith_bin_op, compile_bitwise_or);
define_builtin_func("_^_", arith_bin_op, compile_bitwise_xor);
define_builtin_func("^_+=_", arith_bin_op, compile_add);
define_builtin_func("^_-=_", arith_bin_op, compile_sub);
define_builtin_func("^_*=_", arith_bin_op, compile_mul);
define_builtin_func("^_/=_", arith_bin_op, std::bind(compile_div, _1, _2, _3, -1));
define_builtin_func("^_%=_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, -1));
define_builtin_func("^_<<=_", arith_bin_op, compile_lshift);
define_builtin_func("^_>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, -1));
define_builtin_func("^_&=_", arith_bin_op, compile_bitwise_and);
define_builtin_func("^_|=_", arith_bin_op, compile_bitwise_or);
define_builtin_func("^_^=_", arith_bin_op, compile_bitwise_xor);
define_builtin_func("mulDivFloor", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, -1));
define_builtin_func("mulDivRound", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, 0));
define_builtin_func("mulDivCeil", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, 1));
define_builtin_func("mulDivMod", TypeExpr::new_map(Int3, Int2), AsmOp::Custom("MULDIVMOD", 3, 2));
define_builtin_func("_==_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 2));
define_builtin_func("_!=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 5));
define_builtin_func("_<_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 4));
define_builtin_func("_>_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 1));
define_builtin_func("_<=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 6));
define_builtin_func("_>=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 3));
define_builtin_func("_<=>_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 7));
define_builtin_func("__true", TypeExpr::new_map(TypeExpr::new_unit(), Int), /* AsmOp::Const("TRUE") */ std::bind(compile_bool_const, _1, _2, true));
define_builtin_func("__false", TypeExpr::new_map(TypeExpr::new_unit(), Int), /* AsmOp::Const("FALSE") */ std::bind(compile_bool_const, _1, _2, false));
define_builtin_func("__null", TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_unit(), X)), AsmOp::Const("PUSHNULL"));
define_builtin_func("__isNull", TypeExpr::new_forall({X}, TypeExpr::new_map(X, Int)), compile_is_null);
define_builtin_func("__throw", impure_un_op, compile_throw, true);
define_builtin_func("__throw_arg", throw_arg_op, compile_throw_arg, true);
define_builtin_func("__throw_if_unless", TypeExpr::new_map(Int3, Unit), std::bind(compile_throw_if_unless, _1, _2), true);
define_builtin_func("loadInt", fetch_int_op, std::bind(compile_fetch_int, _1, _2, true, true), {}, {1, 0});
define_builtin_func("loadUint", fetch_int_op, std::bind(compile_fetch_int, _1, _2, true, false), {}, {1, 0});
define_builtin_func("loadBits", fetch_slice_op, std::bind(compile_fetch_slice, _1, _2, true), {}, {1, 0});
define_builtin_func("preloadInt", prefetch_int_op, std::bind(compile_fetch_int, _1, _2, false, true));
define_builtin_func("preloadUint", prefetch_int_op, std::bind(compile_fetch_int, _1, _2, false, false));
define_builtin_func("preloadBits", prefetch_slice_op, std::bind(compile_fetch_slice, _1, _2, false));
define_builtin_func("storeInt", store_int_op, std::bind(compile_store_int, _1, _2, true), {1, 0, 2});
define_builtin_func("storeUint", store_int_op, std::bind(compile_store_int, _1, _2, false), {1, 0, 2});
define_builtin_func("~storeInt", store_int_method, std::bind(compile_store_int, _1, _2, true), {1, 0, 2});
define_builtin_func("~storeUint", store_int_method, std::bind(compile_store_int, _1, _2, false), {1, 0, 2});
define_builtin_func("tupleAt", TypeExpr::new_forall({X}, TypeExpr::new_map(TupleInt, X)), compile_tuple_at);
define_builtin_func("_+_", arith_bin_op, compile_add,
SymValFunc::flagMarkedAsPure);
define_builtin_func("_-_", arith_bin_op, compile_sub,
SymValFunc::flagMarkedAsPure);
define_builtin_func("-_", arith_un_op, compile_unary_minus,
SymValFunc::flagMarkedAsPure);
define_builtin_func("+_", arith_un_op, compile_unary_plus,
SymValFunc::flagMarkedAsPure);
define_builtin_func("_*_", arith_bin_op, compile_mul,
SymValFunc::flagMarkedAsPure);
define_builtin_func("_/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, -1),
SymValFunc::flagMarkedAsPure);
define_builtin_func("_~/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 0),
SymValFunc::flagMarkedAsPure);
define_builtin_func("_^/_", arith_bin_op, std::bind(compile_div, _1, _2, _3, 1),
SymValFunc::flagMarkedAsPure);
define_builtin_func("_%_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, -1),
SymValFunc::flagMarkedAsPure);
define_builtin_func("_<<_", arith_bin_op, compile_lshift,
SymValFunc::flagMarkedAsPure);
define_builtin_func("_>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, -1),
SymValFunc::flagMarkedAsPure);
define_builtin_func("_~>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 0),
SymValFunc::flagMarkedAsPure);
define_builtin_func("_^>>_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, 1),
SymValFunc::flagMarkedAsPure);
define_builtin_func("!_", arith_un_op, compile_logical_not,
SymValFunc::flagMarkedAsPure);
define_builtin_func("~_", arith_un_op, compile_bitwise_not,
SymValFunc::flagMarkedAsPure);
define_builtin_func("_&_", arith_bin_op, compile_bitwise_and,
SymValFunc::flagMarkedAsPure);
define_builtin_func("_|_", arith_bin_op, compile_bitwise_or,
SymValFunc::flagMarkedAsPure);
define_builtin_func("_^_", arith_bin_op, compile_bitwise_xor,
SymValFunc::flagMarkedAsPure);
define_builtin_func("^_+=_", arith_bin_op, compile_add,
SymValFunc::flagMarkedAsPure);
define_builtin_func("^_-=_", arith_bin_op, compile_sub,
SymValFunc::flagMarkedAsPure);
define_builtin_func("^_*=_", arith_bin_op, compile_mul,
SymValFunc::flagMarkedAsPure);
define_builtin_func("^_/=_", arith_bin_op, std::bind(compile_div, _1, _2, _3, -1),
SymValFunc::flagMarkedAsPure);
define_builtin_func("^_%=_", arith_bin_op, std::bind(compile_mod, _1, _2, _3, -1),
SymValFunc::flagMarkedAsPure);
define_builtin_func("^_<<=_", arith_bin_op, compile_lshift,
SymValFunc::flagMarkedAsPure);
define_builtin_func("^_>>=_", arith_bin_op, std::bind(compile_rshift, _1, _2, _3, -1),
SymValFunc::flagMarkedAsPure);
define_builtin_func("^_&=_", arith_bin_op, compile_bitwise_and,
SymValFunc::flagMarkedAsPure);
define_builtin_func("^_|=_", arith_bin_op, compile_bitwise_or,
SymValFunc::flagMarkedAsPure);
define_builtin_func("^_^=_", arith_bin_op, compile_bitwise_xor,
SymValFunc::flagMarkedAsPure);
define_builtin_func("_==_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 2),
SymValFunc::flagMarkedAsPure);
define_builtin_func("_!=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 5),
SymValFunc::flagMarkedAsPure);
define_builtin_func("_<_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 4),
SymValFunc::flagMarkedAsPure);
define_builtin_func("_>_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 1),
SymValFunc::flagMarkedAsPure);
define_builtin_func("_<=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 6),
SymValFunc::flagMarkedAsPure);
define_builtin_func("_>=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 3),
SymValFunc::flagMarkedAsPure);
define_builtin_func("_<=>_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 7),
SymValFunc::flagMarkedAsPure);
define_builtin_func("mulDivFloor", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, -1),
SymValFunc::flagMarkedAsPure);
define_builtin_func("mulDivRound", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, 0),
SymValFunc::flagMarkedAsPure);
define_builtin_func("mulDivCeil", TypeExpr::new_map(Int3, Int), std::bind(compile_muldiv, _1, _2, _3, 1),
SymValFunc::flagMarkedAsPure);
define_builtin_func("mulDivMod", TypeExpr::new_map(Int3, Int2), AsmOp::Custom("MULDIVMOD", 3, 2),
SymValFunc::flagMarkedAsPure);
define_builtin_func("__true", TypeExpr::new_map(TypeExpr::new_unit(), Int), /* AsmOp::Const("TRUE") */ std::bind(compile_bool_const, _1, _2, true),
SymValFunc::flagMarkedAsPure);
define_builtin_func("__false", TypeExpr::new_map(TypeExpr::new_unit(), Int), /* AsmOp::Const("FALSE") */ std::bind(compile_bool_const, _1, _2, false),
SymValFunc::flagMarkedAsPure);
define_builtin_func("__null", TypeExpr::new_forall({X}, TypeExpr::new_map(TypeExpr::new_unit(), X)), AsmOp::Const("PUSHNULL"),
SymValFunc::flagMarkedAsPure);
define_builtin_func("__isNull", TypeExpr::new_forall({X}, TypeExpr::new_map(X, Int)), compile_is_null,
SymValFunc::flagMarkedAsPure);
define_builtin_func("__throw", impure_un_op, compile_throw,
0);
define_builtin_func("__throw_arg", throw_arg_op, compile_throw_arg,
0);
define_builtin_func("__throw_if_unless", TypeExpr::new_map(Int3, Unit), compile_throw_if_unless,
0);
define_builtin_func("loadInt", fetch_int_op_mutate, std::bind(compile_fetch_int, _1, _2, true, true),
SymValFunc::flagMarkedAsPure | SymValFunc::flagHasMutateParams | SymValFunc::flagAcceptsSelf, {}, {1, 0});
define_builtin_func("loadUint", fetch_int_op_mutate, std::bind(compile_fetch_int, _1, _2, true, false),
SymValFunc::flagMarkedAsPure | SymValFunc::flagHasMutateParams | SymValFunc::flagAcceptsSelf, {}, {1, 0});
define_builtin_func("loadBits", fetch_slice_op_mutate, std::bind(compile_fetch_slice, _1, _2, true),
SymValFunc::flagMarkedAsPure | SymValFunc::flagHasMutateParams | SymValFunc::flagAcceptsSelf, {}, {1, 0});
define_builtin_func("preloadInt", prefetch_int_op, std::bind(compile_fetch_int, _1, _2, false, true),
SymValFunc::flagMarkedAsPure | SymValFunc::flagAcceptsSelf);
define_builtin_func("preloadUint", prefetch_int_op, std::bind(compile_fetch_int, _1, _2, false, false),
SymValFunc::flagMarkedAsPure | SymValFunc::flagAcceptsSelf);
define_builtin_func("preloadBits", prefetch_slice_op, std::bind(compile_fetch_slice, _1, _2, false),
SymValFunc::flagMarkedAsPure | SymValFunc::flagAcceptsSelf);
define_builtin_func("storeInt", store_int_mutate, std::bind(compile_store_int, _1, _2, true),
SymValFunc::flagMarkedAsPure | SymValFunc::flagHasMutateParams | SymValFunc::flagAcceptsSelf | SymValFunc::flagReturnsSelf, {1, 0, 2}, {});
define_builtin_func("storeUint", store_int_mutate, std::bind(compile_store_int, _1, _2, false),
SymValFunc::flagMarkedAsPure | SymValFunc::flagHasMutateParams | SymValFunc::flagAcceptsSelf | SymValFunc::flagReturnsSelf, {1, 0, 2}, {});
define_builtin_func("tupleAt", TypeExpr::new_forall({X}, TypeExpr::new_map(TupleInt, X)), compile_tuple_at,
SymValFunc::flagMarkedAsPure | SymValFunc::flagAcceptsSelf);
define_builtin_func("debugPrint", TypeExpr::new_forall({X}, TypeExpr::new_map(X, Unit)),
AsmOp::Custom("s0 DUMP DROP", 1, 1), true);
AsmOp::Custom("s0 DUMP DROP", 1, 1),
0);
define_builtin_func("debugPrintString", TypeExpr::new_forall({X}, TypeExpr::new_map(X, Unit)),
AsmOp::Custom("STRDUMP DROP", 1, 1), true);
AsmOp::Custom("STRDUMP DROP", 1, 1),
0);
define_builtin_func("debugDumpStack", TypeExpr::new_map(Unit, Unit),
AsmOp::Custom("DUMPSTK", 0, 0), true);
AsmOp::Custom("DUMPSTK", 0, 0),
0);
}
} // namespace tolk

View file

@ -41,21 +41,22 @@ Expr::Expr(ExprCls c, sym_idx_t name_idx, std::initializer_list<Expr*> _arglist)
}
}
bool Expr::deduce_type() {
void Expr::deduce_type() {
if (e_type) {
return true;
return;
}
switch (cls) {
case _Apply: {
if (!sym) {
return false;
return;
}
SymVal* sym_val = dynamic_cast<SymVal*>(sym->value);
SymValFunc* sym_val = dynamic_cast<SymValFunc*>(sym->value);
if (!sym_val || !sym_val->get_type()) {
return false;
return;
}
std::vector<TypeExpr*> arg_types;
for (const auto& arg : args) {
arg_types.reserve(args.size());
for (const Expr* arg : args) {
arg_types.push_back(arg->e_type);
}
TypeExpr* fun_type = TypeExpr::new_map(TypeExpr::new_tensor(arg_types), TypeExpr::new_hole());
@ -69,7 +70,7 @@ bool Expr::deduce_type() {
}
e_type = fun_type->args[1];
TypeExpr::remove_indirect(e_type);
return true;
return;
}
case _VarApply: {
tolk_assert(args.size() == 2);
@ -84,7 +85,27 @@ bool Expr::deduce_type() {
}
e_type = fun_type->args[1];
TypeExpr::remove_indirect(e_type);
return true;
return;
}
case _GrabMutatedVars: {
tolk_assert(args.size() == 2 && args[0]->cls == _Apply && sym);
SymValFunc* called_f = dynamic_cast<SymValFunc*>(sym->value);
tolk_assert(called_f->has_mutate_params());
TypeExpr* sym_type = called_f->get_type();
if (sym_type->constr == TypeExpr::te_ForAll) {
TypeExpr::remove_forall(sym_type);
}
tolk_assert(sym_type->args[1]->constr == TypeExpr::te_Tensor);
e_type = sym_type->args[1]->args[sym_type->args[1]->args.size() - 1];
TypeExpr::remove_indirect(e_type);
return;
}
case _ReturnSelf: {
tolk_assert(args.size() == 2 && sym);
Expr* this_arg = args[1];
e_type = this_arg->e_type;
TypeExpr::remove_indirect(e_type);
return;
}
case _Letop: {
tolk_assert(args.size() == 2);
@ -99,25 +120,7 @@ bool Expr::deduce_type() {
}
e_type = args[0]->e_type;
TypeExpr::remove_indirect(e_type);
return true;
}
case _LetFirst: {
tolk_assert(args.size() == 2);
TypeExpr* rhs_type = TypeExpr::new_tensor({args[0]->e_type, TypeExpr::new_hole()});
try {
// std::cerr << "in implicit assignment of a modifying method: " << rhs_type << " and " << args[1]->e_type << std::endl;
unify(rhs_type, args[1]->e_type);
} catch (UnifyError& ue) {
std::ostringstream os;
os << "cannot implicitly assign an expression of type " << args[1]->e_type
<< " to a variable or pattern of type " << rhs_type << " in modifying method `" << G.symbols.get_name(val)
<< "` : " << ue;
throw ParseError(here, os.str());
}
e_type = rhs_type->args[1];
TypeExpr::remove_indirect(e_type);
// std::cerr << "result type is " << e_type << std::endl;
return true;
return;
}
case _CondExpr: {
tolk_assert(args.size() == 3);
@ -139,46 +142,46 @@ bool Expr::deduce_type() {
}
e_type = args[1]->e_type;
TypeExpr::remove_indirect(e_type);
return true;
return;
}
default:
throw Fatal("unexpected cls=" + std::to_string(cls) + " in Expr::deduce_type()");
}
return false;
}
int Expr::define_new_vars(CodeBlob& code) {
void Expr::define_new_vars(CodeBlob& code) {
switch (cls) {
case _Tensor:
case _MkTuple: {
int res = 0;
for (const auto& x : args) {
res += x->define_new_vars(code);
for (Expr* item : args) {
item->define_new_vars(code);
}
return res;
break;
}
case _Var:
if (val < 0) {
val = code.create_var(false, e_type, sym, here);
return 1;
val = code.create_var(e_type, sym->sym_idx, here);
sym->value->idx = val;
}
break;
case _Hole:
if (val < 0) {
val = code.create_var(true, e_type, nullptr, here);
val = code.create_tmp_var(e_type, here);
}
break;
default:
break;
}
return 0;
}
int Expr::predefine_vars() {
void Expr::predefine_vars() {
switch (cls) {
case _Tensor:
case _MkTuple: {
int res = 0;
for (const auto& x : args) {
res += x->predefine_vars();
for (Expr* item : args) {
item->predefine_vars();
}
return res;
break;
}
case _Var:
if (!sym) {
@ -188,12 +191,15 @@ int Expr::predefine_vars() {
if (!sym) {
throw ParseError{here, std::string{"redefined variable `"} + G.symbols.get_name(~val) + "`"};
}
sym->value = new SymVal{SymValKind::_Var, -1, e_type};
return 1;
sym->value = new SymValVariable(-1, e_type);
if (is_immutable()) {
dynamic_cast<SymValVariable*>(sym->value)->flags |= SymValVariable::flagImmutable;
}
}
break;
default:
break;
}
return 0;
}
var_idx_t Expr::new_tmp(CodeBlob& code) const {
@ -217,7 +223,7 @@ std::vector<var_idx_t> pre_compile_let(CodeBlob& code, Expr* lhs, Expr* rhs, Src
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{Expr::_Var};
auto tvar = new Expr{Expr::_Var, lhs->here};
tvar->set_val(tmp[0]);
tvar->set_location(rhs->here);
tvar->e_type = unpacked_type;
@ -255,7 +261,7 @@ std::vector<var_idx_t> pre_compile_tensor(const std::vector<Expr *>& args, CodeB
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 (!lval_globs && !var.is_tmp_unnamed) {
if (!lval_globs && !var.is_unnamed()) {
var.on_modification.push_back([&modified_vars, i, j, cur_ops = code.cur_ops, done = false](SrcLocation here) mutable {
if (!done) {
done = true;
@ -303,39 +309,39 @@ std::vector<var_idx_t> Expr::pre_compile(CodeBlob& code, std::vector<std::pair<S
}
case _Apply: {
tolk_assert(sym);
std::vector<var_idx_t> res;
SymDef* applied_sym = sym;
auto func = dynamic_cast<SymValFunc*>(applied_sym->value);
// replace `beginCell()` with `begin_cell()`
// todo it should be done at AST level, see comment above detect_if_function_just_wraps_another()
if (func && func->is_just_wrapper_for_another_f()) {
// todo currently, f is inlined only if anotherF is declared (and processed) before
if (!dynamic_cast<SymValCodeFunc*>(func)->code) { // if anotherF is processed after
func->flags |= SymValFunc::flagUsedAsNonCall;
res = pre_compile_tensor(args, code, lval_globs);
} else {
// 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);
}
std::vector<var_idx_t> res = pre_compile_tensor(args, code, lval_globs);;
auto rvect = new_tmp_vect(code);
auto& op = code.emplace_back(here, Op::_Call, rvect, res, applied_sym);
auto& op = code.emplace_back(here, Op::_Call, rvect, res, sym);
if (flags & _IsImpure) {
op.set_impure(code);
}
return rvect;
}
case _GrabMutatedVars: {
SymValFunc* func_val = dynamic_cast<SymValFunc*>(sym->value);
tolk_assert(func_val && func_val->has_mutate_params());
tolk_assert(args.size() == 2 && args[0]->cls == _Apply && args[1]->cls == _Tensor);
auto right = args[0]->pre_compile(code); // apply (returning function result and mutated)
std::vector<std::pair<SymDef*, var_idx_t>> local_globs;
if (!lval_globs) {
lval_globs = &local_globs;
}
auto left = args[1]->pre_compile(code, lval_globs); // mutated (lvalue)
auto rvect = new_tmp_vect(code);
left.push_back(rvect[0]);
for (var_idx_t v : left) {
code.on_var_modification(v, here);
}
code.emplace_back(here, Op::_Let, std::move(left), std::move(right));
add_set_globs(code, local_globs, here);
return rvect;
}
case _ReturnSelf: {
tolk_assert(args.size() == 2 && sym);
Expr* this_arg = args[1];
auto right = args[0]->pre_compile(code);
return this_arg->pre_compile(code);
}
case _Var:
case _Hole:
if (val < 0) {
@ -372,7 +378,10 @@ std::vector<var_idx_t> Expr::pre_compile(CodeBlob& code, std::vector<std::pair<S
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");
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");
}
if (fun_ref->has_mutate_params()) {
throw ParseError(here, "saving `" + sym->name() + "` into a variable is impossible, since it has `mutate` parameters and thus can only be called directly");
}
}
auto rvect = new_tmp_vect(code);
@ -387,22 +396,6 @@ std::vector<var_idx_t> Expr::pre_compile(CodeBlob& code, std::vector<std::pair<S
case _Letop: {
return pre_compile_let(code, args.at(0), args.at(1), here);
}
case _LetFirst: {
auto rvect = new_tmp_vect(code);
auto right = args[1]->pre_compile(code);
std::vector<std::pair<SymDef*, var_idx_t>> local_globs;
if (!lval_globs) {
lval_globs = &local_globs;
}
auto left = args[0]->pre_compile(code, lval_globs);
left.push_back(rvect[0]);
for (var_idx_t v : left) {
code.on_var_modification(v, here);
}
code.emplace_back(here, Op::_Let, std::move(left), std::move(right));
add_set_globs(code, local_globs, here);
return rvect;
}
case _MkTuple: {
auto left = new_tmp_vect(code);
auto right = args[0]->pre_compile(code);

View file

@ -323,8 +323,7 @@ struct ChunkIdentifierOrKeyword final : ChunkLexerBase {
static TokenType maybe_keyword(std::string_view str) {
switch (str.size()) {
case 1:
if (str == "~") return tok_bitwise_not; // todo attention
if (str == "_") return tok_underscore; // todo attention
if (str == "_") return tok_underscore;
break;
case 2:
if (str == "do") return tok_do;
@ -347,6 +346,7 @@ struct ChunkIdentifierOrKeyword final : ChunkLexerBase {
if (str == "void") return tok_void;
if (str == "bool") return tok_bool;
if (str == "auto") return tok_auto;
if (str == "self") return tok_self;
if (str == "tolk") return tok_tolk;
if (str == "type") return tok_type;
if (str == "enum") return tok_enum;
@ -368,6 +368,7 @@ struct ChunkIdentifierOrKeyword final : ChunkLexerBase {
if (str == "assert") return tok_assert;
if (str == "import") return tok_import;
if (str == "global") return tok_global;
if (str == "mutate") return tok_mutate;
if (str == "repeat") return tok_repeat;
if (str == "struct") return tok_struct;
if (str == "export") return tok_export;
@ -394,8 +395,7 @@ struct ChunkIdentifierOrKeyword final : ChunkLexerBase {
lex->skip_chars(1);
while (!lex->is_eof()) {
char c = lex->char_at();
// the pattern of valid identifier first symbol is provided in trie, here we test for identifier middle
bool allowed_in_identifier = std::isalnum(c) || c == '_' || c == '$' || c == '?' || c == '!' || c == '\'';
bool allowed_in_identifier = std::isalnum(c) || c == '_' || c == '$';
if (!allowed_in_identifier) {
break;
}
@ -438,28 +438,6 @@ struct ChunkIdentifierInBackticks final : ChunkLexerBase {
}
};
// Handle ~`some_method` and .`some_method` todo to be removed later
struct ChunkDotTildeAndBackticks final : ChunkLexerBase {
bool parse(Lexer* lex) const override {
const char* str_begin = lex->c_str();
lex->skip_chars(2);
while (!lex->is_eof() && lex->char_at() != '`' && lex->char_at() != '\n') {
lex->skip_chars(1);
}
if (lex->char_at() != '`') {
lex->error("unclosed backtick `");
}
std::string_view in_backticks(str_begin + 2, lex->c_str() - str_begin - 2);
std::string full = std::string(1, *str_begin) + static_cast<std::string>(in_backticks);
std::string* allocated = new std::string(full);
lex->skip_chars(1);
std::string_view str_val(allocated->c_str(), allocated->size());
lex->add_token(tok_identifier, str_val);
return true;
}
};
//
// ----------------------------------------------------------------------
// Here we define a grammar of Tolk.
@ -500,11 +478,8 @@ struct TolkLanguageGrammar {
trie.add_prefix("\n", singleton<ChunkSkipWhitespace>());
trie.add_pattern("[0-9]", singleton<ChunkNumber>());
// todo think of . ~
trie.add_pattern("[a-zA-Z_$.~]", singleton<ChunkIdentifierOrKeyword>());
trie.add_pattern("[a-zA-Z_$]", singleton<ChunkIdentifierOrKeyword>());
trie.add_prefix("`", singleton<ChunkIdentifierInBackticks>());
// todo to be removed after ~ becomes invalid and . becomes a separate token
trie.add_pattern("[.~]`", singleton<ChunkDotTildeAndBackticks>());
register_token("+", 1, tok_plus);
register_token("-", 1, tok_minus);
@ -528,6 +503,8 @@ struct TolkLanguageGrammar {
register_token("&", 1, tok_bitwise_and);
register_token("|", 1, tok_bitwise_or);
register_token("^", 1, tok_bitwise_xor);
register_token("~", 1, tok_bitwise_not);
register_token(".", 1, tok_dot);
register_token("==", 2, tok_eq);
register_token("!=", 2, tok_neq);
register_token("<=", 2, tok_leq);

View file

@ -38,6 +38,8 @@ enum TokenType {
tok_var,
tok_val,
tok_redef,
tok_mutate,
tok_self,
tok_annotation_at,
tok_colon,
@ -52,6 +54,7 @@ enum TokenType {
tok_null,
tok_identifier,
tok_dot,
tok_plus,
tok_minus,

View file

@ -37,7 +37,87 @@ static int calc_sym_idx(std::string_view sym_name) {
return G.symbols.lookup(sym_name);
}
void Expr::fire_error_rvalue_expected() const {
// generally, almost all vertices are rvalue, that's why code leading to "not rvalue"
// should be very strange, like `var x = _`
throw ParseError(here, "rvalue expected");
}
void Expr::fire_error_lvalue_expected(const std::string& details) const {
// "lvalue expected" is when a user modifies something unmodifiable
// example: `f() = 32`
// example: `loadUint(c.beginParse(), 32)` (since `loadUint()` mutates the first argument)
throw ParseError(here, "lvalue expected (" + details + ")");
}
void Expr::fire_error_modifying_immutable(const std::string& details) const {
// "modifying immutable variable" is when a user assigns to a variable declared `val`
// example: `immutable_val = 32`
// example: `(regular_var, immutable_val) = f()`
// for better error message, try to print out variable name if possible
std::string variable_name;
if (cls == _Var || cls == _Const) {
variable_name = sym->name();
} else if (cls == _Tensor || cls == _MkTuple) {
for (const Expr* arg : (cls == _Tensor ? args : args[0]->args)) {
if (arg->is_immutable() && (arg->cls == _Var || arg->cls == _Const)) {
variable_name = arg->sym->name();
break;
}
}
}
if (variable_name == "self") {
throw ParseError(here, "modifying `self` (" + details + "), which is immutable by default; probably, you want to declare `mutate self`");
} else if (!variable_name.empty()) {
throw ParseError(here, "modifying an immutable variable `" + variable_name + "` (" + details + ")");
} else {
throw ParseError(here, "modifying an immutable variable (" + details + ")");
}
}
GNU_ATTRIBUTE_COLD GNU_ATTRIBUTE_NORETURN
static void fire_error_invalid_mutate_arg_passed(SrcLocation loc, const SymDef* func_sym, const SymDef* param_sym, bool called_as_method, bool arg_passed_as_mutate, AnyV arg_expr) {
std::string func_name = func_sym->name();
std::string arg_str(arg_expr->type == ast_identifier ? arg_expr->as<ast_identifier>()->name : "obj");
const SymValFunc* func_val = dynamic_cast<const SymValFunc*>(func_sym->value);
const SymValVariable* param_val = dynamic_cast<const SymValVariable*>(param_sym->value);
// case: `loadInt(cs, 32)`; suggest: `cs.loadInt(32)`
if (param_val->is_mutate_parameter() && !arg_passed_as_mutate && !called_as_method && param_val->idx == 0 && func_val->does_accept_self()) {
throw ParseError(loc, "`" + func_name + "` is a mutating method; consider calling `" + arg_str + "." + func_name + "()`, not `" + func_name + "(" + arg_str + ")`");
}
// case: `cs.mutating_function()`; suggest: `mutating_function(mutate cs)` or make it a method
if (param_val->is_mutate_parameter() && called_as_method && param_val->idx == 0 && !func_val->does_accept_self()) {
throw ParseError(loc, "function `" + func_name + "` mutates parameter `" + param_sym->name() + "`; consider calling `" + func_name + "(mutate " + arg_str + ")`, not `" + arg_str + "." + func_name + "`(); alternatively, rename parameter to `self` to make it a method");
}
// case: `mutating_function(arg)`; suggest: `mutate arg`
if (param_val->is_mutate_parameter() && !arg_passed_as_mutate) {
throw ParseError(loc, "function `" + func_name + "` mutates parameter `" + param_sym->name() + "`; you need to specify `mutate` when passing an argument, like `mutate " + arg_str + "`");
}
// case: `usual_function(mutate arg)`
if (!param_val->is_mutate_parameter() && arg_passed_as_mutate) {
throw ParseError(loc, "incorrect `mutate`, since `" + func_name + "` does not mutate this parameter");
}
throw Fatal("unreachable");
}
namespace blk_fl {
enum { end = 1, ret = 2, empty = 4 };
typedef int val;
constexpr val init = end | empty;
void combine(val& x, const val y) {
x |= y & ret;
x &= y | ~(end | empty);
}
void combine_parallel(val& x, const val y) {
x &= y | ~(ret | empty);
x |= y & end;
}
} // namespace blk_fl
Expr* process_expr(AnyV v, CodeBlob& code);
blk_fl::val process_statement(AnyV v, CodeBlob& code);
static void check_global_func(SrcLocation loc, sym_idx_t func_name) {
SymDef* sym_def = lookup_symbol(func_name);
@ -46,22 +126,6 @@ static void check_global_func(SrcLocation loc, sym_idx_t func_name) {
}
}
static Expr* make_func_apply(Expr* fun, Expr* x) {
Expr* res{nullptr};
if (fun->cls == Expr::_GlobFunc) {
if (x->cls == Expr::_Tensor) {
res = new Expr{Expr::_Apply, fun->sym, x->args};
} else {
res = new Expr{Expr::_Apply, fun->sym, {x}};
}
res->flags = Expr::_IsRvalue | (fun->flags & Expr::_IsImpure);
} else {
res = new Expr{Expr::_VarApply, {fun, x}};
res->flags = Expr::_IsRvalue;
}
return res;
}
static void check_import_exists_when_using_sym(AnyV v_usage, const SymDef* used_sym) {
if (!v_usage->loc.is_symbol_from_same_or_builtin_file(used_sym->loc)) {
const SrcFile* declared_in = used_sym->loc.get_src_file();
@ -77,7 +141,7 @@ static void check_import_exists_when_using_sym(AnyV v_usage, const SymDef* used_
}
}
static Expr* create_new_local_variable(SrcLocation loc, std::string_view var_name, TypeExpr* var_type) {
static Expr* create_new_local_variable(SrcLocation loc, std::string_view var_name, TypeExpr* var_type, bool is_immutable) {
SymDef* sym = lookup_symbol(calc_sym_idx(var_name));
if (sym) { // creating a new variable, but something found in symtable
if (sym->level != G.scope_level) {
@ -89,7 +153,7 @@ static Expr* create_new_local_variable(SrcLocation loc, std::string_view var_nam
Expr* x = new Expr{Expr::_Var, loc};
x->val = ~calc_sym_idx(var_name);
x->e_type = var_type;
x->flags = Expr::_IsLvalue;
x->flags = Expr::_IsLvalue | (is_immutable ? Expr::_IsImmutable : 0);
return x;
}
@ -109,8 +173,13 @@ static Expr* process_expr(V<ast_binary_operator> v, CodeBlob& code) {
t == tok_set_mod || t == tok_set_lshift || t == tok_set_rshift ||
t == tok_set_bitwise_and || t == tok_set_bitwise_or || t == tok_set_bitwise_xor) {
Expr* x = process_expr(v->get_lhs(), code);
x->chk_lvalue();
x->chk_rvalue();
if (!x->is_lvalue()) {
x->fire_error_lvalue_expected("left side of assignment");
}
if (x->is_immutable()) {
x->fire_error_modifying_immutable("left side of assignment");
}
sym_idx_t name = G.symbols.lookup_add("^_" + operator_name + "_");
Expr* y = process_expr(v->get_rhs(), code);
y->chk_rvalue();
@ -126,7 +195,12 @@ static Expr* process_expr(V<ast_binary_operator> v, CodeBlob& code) {
}
if (t == tok_assign) {
Expr* x = process_expr(v->get_lhs(), code);
x->chk_lvalue();
if (!x->is_lvalue()) {
x->fire_error_lvalue_expected("left side of assignment");
}
if (x->is_immutable()) {
x->fire_error_modifying_immutable("left side of assignment");
}
Expr* y = process_expr(v->get_rhs(), code);
y->chk_rvalue();
x->predefine_vars();
@ -191,54 +265,6 @@ static Expr* process_expr(V<ast_unary_operator> v, CodeBlob& code) {
return res;
}
static Expr* process_expr(V<ast_dot_tilde_call> v, CodeBlob& code) {
Expr* res = process_expr(v->get_lhs(), code);
bool modify = v->method_name[0] == '~';
Expr* obj = res;
if (modify) {
obj->chk_lvalue();
} else {
obj->chk_rvalue();
}
sym_idx_t name_idx = calc_sym_idx(v->method_name);
const SymDef* sym = lookup_symbol(name_idx);
if (!sym || !dynamic_cast<SymValFunc*>(sym->value)) {
sym_idx_t name1 = G.symbols.lookup(v->method_name.substr(1));
if (name1) {
const SymDef* sym1 = lookup_symbol(name1);
if (sym1 && dynamic_cast<SymValFunc*>(sym1->value)) {
name_idx = name1;
}
}
}
check_global_func(v->loc, name_idx);
sym = lookup_symbol(name_idx);
SymValFunc* val = sym ? dynamic_cast<SymValFunc*>(sym->value) : nullptr;
if (!val) {
v->error("undefined method call");
}
Expr* x = process_expr(v->get_arg(), code);
x->chk_rvalue();
if (x->cls == Expr::_Tensor) {
res = new Expr{Expr::_Apply, name_idx, {obj}};
res->args.insert(res->args.end(), x->args.begin(), x->args.end());
} else {
res = new Expr{Expr::_Apply, name_idx, {obj, x}};
}
res->here = v->loc;
res->flags = Expr::_IsRvalue | (val->is_marked_as_pure() ? 0 : Expr::_IsImpure);
res->deduce_type();
if (modify) {
Expr* tmp = res;
res = new Expr{Expr::_LetFirst, {obj->copy(), tmp}};
res->here = v->loc;
res->flags = tmp->flags;
res->set_val(name_idx);
res->deduce_type();
}
return res;
}
static Expr* process_expr(V<ast_ternary_operator> v, CodeBlob& code) {
Expr* cond = process_expr(v->get_cond(), code);
cond->chk_rvalue();
@ -253,19 +279,194 @@ static Expr* process_expr(V<ast_ternary_operator> v, CodeBlob& code) {
return res;
}
static Expr* process_expr(V<ast_function_call> v, CodeBlob& code) {
static Expr* process_function_arguments(SymDef* func_sym, V<ast_argument_list> v, Expr* lhs_of_dot_call, CodeBlob& code) {
SymValFunc* func_val = dynamic_cast<SymValFunc*>(func_sym->value);
int delta_self = lhs_of_dot_call ? 1 : 0;
int n_arguments = static_cast<int>(v->get_arguments().size()) + delta_self;
int n_parameters = static_cast<int>(func_val->parameters.size());
// Tolk doesn't have optional parameters currently, so just compare counts
if (n_parameters < n_arguments) {
v->error("too many arguments in call to `" + func_sym->name() + "`, expected " + std::to_string(n_parameters - delta_self) + ", have " + std::to_string(n_arguments - delta_self));
}
if (n_arguments < n_parameters) {
v->error("too few arguments in call to `" + func_sym->name() + "`, expected " + std::to_string(n_parameters - delta_self) + ", have " + std::to_string(n_arguments - delta_self));
}
std::vector<Expr*> apply_args;
apply_args.reserve(n_arguments);
if (lhs_of_dot_call) {
apply_args.push_back(lhs_of_dot_call);
}
for (int i = delta_self; i < n_arguments; ++i) {
auto v_arg = v->get_arg(i - delta_self);
if (SymDef* param_sym = func_val->parameters[i]) { // can be null (for underscore parameter)
SymValVariable* param_val = dynamic_cast<SymValVariable*>(param_sym->value);
if (param_val->is_mutate_parameter() != v_arg->passed_as_mutate) {
fire_error_invalid_mutate_arg_passed(v_arg->loc, func_sym, param_sym, false, v_arg->passed_as_mutate, v_arg->get_expr());
}
}
Expr* arg = process_expr(v_arg->get_expr(), code);
arg->chk_rvalue();
apply_args.push_back(arg);
}
Expr* apply = new Expr{Expr::_Apply, func_sym, std::move(apply_args)};
apply->flags = Expr::_IsRvalue | (!func_val->is_marked_as_pure() * Expr::_IsImpure);
apply->here = v->loc;
apply->deduce_type();
return apply;
}
static Expr* process_function_call(V<ast_function_call> v, CodeBlob& code) {
// special error for "null()" which is a FunC syntax
if (v->get_called_f()->type == ast_null_keyword) {
v->error("null is not a function: use `null`, not `null()`");
}
Expr* res = process_expr(v->get_called_f(), code);
Expr* x = process_expr(v->get_called_arg(), code);
x->chk_rvalue();
res = make_func_apply(res, x);
res->here = v->loc;
res->deduce_type();
return res;
// most likely it's a global function, but also may be `some_var(args)` or even `getF()(args)`
Expr* lhs = process_expr(v->get_called_f(), code);
if (lhs->cls != Expr::_GlobFunc) {
Expr* tensor_arg = new Expr(Expr::_Tensor, v->loc);
std::vector<TypeExpr*> type_list;
type_list.reserve(v->get_num_args());
for (int i = 0; i < v->get_num_args(); ++i) {
auto v_arg = v->get_arg(i);
if (v_arg->passed_as_mutate) {
v_arg->error("`mutate` used for non-mutate argument");
}
Expr* arg = process_expr(v_arg->get_expr(), code);
arg->chk_rvalue();
tensor_arg->pb_arg(arg);
type_list.push_back(arg->e_type);
}
tensor_arg->flags = Expr::_IsRvalue;
tensor_arg->e_type = TypeExpr::new_tensor(std::move(type_list));
Expr* var_apply = new Expr{Expr::_VarApply, {lhs, tensor_arg}};
var_apply->here = v->loc;
var_apply->flags = Expr::_IsRvalue;
var_apply->deduce_type();
return var_apply;
}
Expr* apply = process_function_arguments(lhs->sym, v->get_arg_list(), nullptr, code);
if (dynamic_cast<SymValFunc*>(apply->sym->value)->has_mutate_params()) {
const std::vector<Expr*>& args = apply->args;
SymValFunc* func_val = dynamic_cast<SymValFunc*>(apply->sym->value);
tolk_assert(func_val->parameters.size() == args.size());
Expr* grabbed_vars = new Expr(Expr::_Tensor, v->loc);
std::vector<TypeExpr*> type_list;
for (int i = 0; i < static_cast<int>(args.size()); ++i) {
SymDef* param_def = func_val->parameters[i];
if (param_def && dynamic_cast<SymValVariable*>(param_def->value)->is_mutate_parameter()) {
if (!args[i]->is_lvalue()) {
args[i]->fire_error_lvalue_expected("call a mutating function");
}
if (args[i]->is_immutable()) {
args[i]->fire_error_modifying_immutable("call a mutating function");
}
grabbed_vars->pb_arg(args[i]->copy());
type_list.emplace_back(args[i]->e_type);
}
}
grabbed_vars->flags = Expr::_IsRvalue;
Expr* grab_mutate = new Expr(Expr::_GrabMutatedVars, apply->sym, {apply, grabbed_vars});
grab_mutate->here = v->loc;
grab_mutate->flags = apply->flags;
grab_mutate->deduce_type();
return grab_mutate;
}
return apply;
}
static Expr* process_dot_method_call(V<ast_dot_method_call> v, CodeBlob& code) {
sym_idx_t name_idx = calc_sym_idx(v->method_name);
check_global_func(v->loc, name_idx);
SymDef* func_sym = lookup_symbol(name_idx);
SymValFunc* func_val = dynamic_cast<SymValFunc*>(func_sym->value);
tolk_assert(func_val != nullptr);
Expr* obj = process_expr(v->get_obj(), code);
obj->chk_rvalue();
if (func_val->parameters.empty()) {
v->error("`" + func_sym->name() + "` has no parameters and can not be called as method");
}
if (!func_val->does_accept_self() && func_val->parameters[0] && dynamic_cast<SymValVariable*>(func_val->parameters[0]->value)->is_mutate_parameter()) {
fire_error_invalid_mutate_arg_passed(v->loc, func_sym, func_val->parameters[0], true, false, v->get_obj());
}
Expr* apply = process_function_arguments(func_sym, v->get_arg_list(), obj, code);
Expr* obj_lval = apply->args[0];
if (!obj_lval->is_lvalue()) {
if (obj_lval->cls == Expr::_ReturnSelf) {
obj_lval = obj_lval->args[1];
} else {
Expr* tmp_var = create_new_underscore_variable(v->loc, obj_lval->e_type);
tmp_var->define_new_vars(code);
Expr* assign_to_tmp_var = new Expr(Expr::_Letop, {tmp_var, obj_lval});
assign_to_tmp_var->here = v->loc;
assign_to_tmp_var->flags = Expr::_IsRvalue;
assign_to_tmp_var->deduce_type();
apply->args[0] = assign_to_tmp_var;
obj_lval = tmp_var;
}
}
if (func_val->has_mutate_params()) {
tolk_assert(func_val->parameters.size() == apply->args.size());
Expr* grabbed_vars = new Expr(Expr::_Tensor, v->loc);
std::vector<TypeExpr*> type_list;
for (int i = 0; i < static_cast<int>(apply->args.size()); ++i) {
SymDef* param_sym = func_val->parameters[i];
if (param_sym && dynamic_cast<SymValVariable*>(param_sym->value)->is_mutate_parameter()) {
Expr* ith_arg = apply->args[i];
if (ith_arg->is_immutable()) {
ith_arg->fire_error_modifying_immutable("call a mutating method");
}
Expr* var_to_mutate = nullptr;
if (ith_arg->is_lvalue()) {
var_to_mutate = ith_arg->copy();
} else if (i == 0) {
var_to_mutate = obj_lval;
} else {
ith_arg->fire_error_lvalue_expected("call a mutating method");
}
tolk_assert(var_to_mutate->is_lvalue() && !var_to_mutate->is_immutable());
grabbed_vars->pb_arg(var_to_mutate);
type_list.emplace_back(var_to_mutate->e_type);
}
}
grabbed_vars->flags = Expr::_IsRvalue;
Expr* grab_mutate = new Expr(Expr::_GrabMutatedVars, func_sym, {apply, grabbed_vars});
grab_mutate->here = v->loc;
grab_mutate->flags = apply->flags;
grab_mutate->deduce_type();
apply = grab_mutate;
}
if (func_val->does_return_self()) {
Expr* self_arg = obj_lval;
tolk_assert(self_arg->is_lvalue());
Expr* return_self = new Expr(Expr::_ReturnSelf, func_sym, {apply, self_arg});
return_self->here = v->loc;
return_self->flags = Expr::_IsRvalue;
return_self->deduce_type();
apply = return_self;
}
return apply;
}
static Expr* process_expr(V<ast_tensor> v, CodeBlob& code) {
@ -285,7 +486,8 @@ static Expr* process_expr(V<ast_tensor> v, CodeBlob& code) {
for (int i = 1; i < v->size(); ++i) {
Expr* x = process_expr(v->get_item(i), code);
res->pb_arg(x);
f &= x->flags;
f &= (x->flags | Expr::_IsImmutable);
f |= (x->flags & Expr::_IsImmutable);
type_list.push_back(x->e_type);
}
res->here = v->loc;
@ -315,7 +517,8 @@ static Expr* process_expr(V<ast_tensor_square> v, CodeBlob& code) {
for (int i = 1; i < v->size(); ++i) {
Expr* x = process_expr(v->get_item(i), code);
res->pb_arg(x);
f &= x->flags;
f &= (x->flags | Expr::_IsImmutable);
f |= (x->flags & Expr::_IsImmutable);
type_list.push_back(x->e_type);
}
res->here = v->loc;
@ -419,21 +622,36 @@ static Expr* process_expr(V<ast_bool_const> v) {
return res;
}
static Expr* process_expr([[maybe_unused]] V<ast_null_keyword> v) {
static Expr* process_expr(V<ast_null_keyword> v) {
SymDef* builtin_sym = lookup_symbol(calc_sym_idx("__null"));
Expr* res = new Expr{Expr::_Apply, builtin_sym, {}};
res->here = v->loc;
res->flags = Expr::_IsRvalue;
res->deduce_type();
return res;
}
static Expr* process_expr(V<ast_self_keyword> v, CodeBlob& code) {
if (!code.func_val->does_accept_self()) {
v->error("using `self` in a non-member function (it does not accept the first `self` parameter)");
}
SymDef* sym = lookup_symbol(calc_sym_idx("self"));
tolk_assert(sym);
SymValVariable* sym_val = dynamic_cast<SymValVariable*>(sym->value);
Expr* res = new Expr(Expr::_Var, v->loc);
res->sym = sym;
res->val = sym_val->idx;
res->flags = Expr::_IsLvalue | Expr::_IsRvalue | (sym_val->is_immutable() ? Expr::_IsImmutable : 0);
res->e_type = sym_val->get_type();
return res;
}
static Expr* process_identifier(V<ast_identifier> v) {
SymDef* sym = lookup_symbol(calc_sym_idx(v->name));
if (sym && dynamic_cast<SymValGlobVar*>(sym->value)) {
check_import_exists_when_using_sym(v, sym);
auto val = dynamic_cast<SymValGlobVar*>(sym->value);
Expr* res = new Expr{Expr::_GlobVar, v->loc};
res->e_type = val->get_type();
res->e_type = sym->value->get_type();
res->sym = sym;
res->flags = Expr::_IsLvalue | Expr::_IsRvalue | Expr::_IsImpure;
return res;
@ -441,19 +659,20 @@ static Expr* process_identifier(V<ast_identifier> v) {
if (sym && dynamic_cast<SymValConst*>(sym->value)) {
check_import_exists_when_using_sym(v, sym);
auto val = dynamic_cast<SymValConst*>(sym->value);
Expr* res = new Expr{Expr::_None, v->loc};
res->flags = Expr::_IsRvalue;
Expr* res = nullptr;
if (val->get_kind() == SymValConst::IntConst) {
res->cls = Expr::_Const;
res = new Expr{Expr::_Const, v->loc};
res->intval = val->get_int_value();
res->e_type = TypeExpr::new_atomic(tok_int);
res->e_type = TypeExpr::new_atomic(TypeExpr::_Int);
} else if (val->get_kind() == SymValConst::SliceConst) {
res->cls = Expr::_SliceConst;
res = new Expr{Expr::_SliceConst, v->loc};
res->strval = val->get_str_value();
res->e_type = TypeExpr::new_atomic(tok_slice);
res->e_type = TypeExpr::new_atomic(TypeExpr::_Slice);
} else {
v->error("invalid symbolic constant type");
}
res->flags = Expr::_IsLvalue | Expr::_IsRvalue | Expr::_IsImmutable;
res->sym = sym;
return res;
}
if (sym && dynamic_cast<SymValFunc*>(sym->value)) {
@ -463,28 +682,26 @@ static Expr* process_identifier(V<ast_identifier> v) {
if (!sym) {
check_global_func(v->loc, calc_sym_idx(v->name));
sym = lookup_symbol(calc_sym_idx(v->name));
tolk_assert(sym);
}
res->sym = sym;
SymVal* val = nullptr;
bool impure = false;
if (sym) {
val = dynamic_cast<SymVal*>(sym->value);
}
if (!val) {
bool immutable = false;
if (const SymValFunc* func_val = dynamic_cast<SymValFunc*>(sym->value)) {
res->e_type = func_val->get_type();
res->cls = Expr::_GlobFunc;
impure = !func_val->is_marked_as_pure();
} else if (const SymValVariable* var_val = dynamic_cast<SymValVariable*>(sym->value)) {
tolk_assert(var_val->idx >= 0)
res->val = var_val->idx;
res->e_type = var_val->get_type();
immutable = var_val->is_immutable();
// std::cerr << "accessing variable " << lex.cur().str << " : " << res->e_type << std::endl;
} else {
v->error("undefined identifier '" + static_cast<std::string>(v->name) + "'");
}
if (val->kind == SymValKind::_Func) {
res->e_type = val->get_type();
res->cls = Expr::_GlobFunc;
impure = !dynamic_cast<SymValFunc*>(val)->is_marked_as_pure();
} else {
tolk_assert(val->idx >= 0);
res->val = val->idx;
res->e_type = val->get_type();
// 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 | (impure ? Expr::_IsImpure : 0);
res->flags = Expr::_IsLvalue | Expr::_IsRvalue | (impure ? Expr::_IsImpure : 0) | (immutable ? Expr::_IsImmutable : 0);
res->deduce_type();
return res;
}
@ -495,12 +712,12 @@ Expr* process_expr(AnyV v, CodeBlob& code) {
return process_expr(v->as<ast_binary_operator>(), code);
case ast_unary_operator:
return process_expr(v->as<ast_unary_operator>(), code);
case ast_dot_tilde_call:
return process_expr(v->as<ast_dot_tilde_call>(), code);
case ast_ternary_operator:
return process_expr(v->as<ast_ternary_operator>(), code);
case ast_function_call:
return process_expr(v->as<ast_function_call>(), code);
return process_function_call(v->as<ast_function_call>(), code);
case ast_dot_method_call:
return process_dot_method_call(v->as<ast_dot_method_call>(), code);
case ast_parenthesized_expr:
return process_expr(v->as<ast_parenthesized_expr>()->get_expr(), code);
case ast_tensor:
@ -515,6 +732,8 @@ Expr* process_expr(AnyV v, CodeBlob& code) {
return process_expr(v->as<ast_bool_const>());
case ast_null_keyword:
return process_expr(v->as<ast_null_keyword>());
case ast_self_keyword:
return process_expr(v->as<ast_self_keyword>(), code);
case ast_identifier:
return process_identifier(v->as<ast_identifier>());
case ast_underscore:
@ -524,31 +743,22 @@ Expr* process_expr(AnyV v, CodeBlob& code) {
}
}
namespace blk_fl {
enum { end = 1, ret = 2, empty = 4 };
typedef int val;
constexpr val init = end | empty;
void combine(val& x, const val y) {
x |= y & ret;
x &= y | ~(end | empty);
}
void combine_parallel(val& x, const val y) {
x &= y | ~(ret | empty);
x |= y & end;
}
} // namespace blk_fl
static Expr* process_local_vars_lhs(AnyV v, CodeBlob& code) {
switch (v->type) {
case ast_local_var: {
if (v->as<ast_local_var>()->marked_as_redef) {
return process_identifier(v->as<ast_local_var>()->get_identifier()->as<ast_identifier>());
auto v_var = v->as<ast_local_var>();
if (v_var->marked_as_redef) {
Expr* redef_var = process_identifier(v_var->get_identifier()->as<ast_identifier>());
if (redef_var->is_immutable()) {
redef_var->fire_error_modifying_immutable("left side of assignment");
}
return redef_var;
}
TypeExpr* declared_type = v->as<ast_local_var>()->declared_type;
TypeExpr* var_type = v_var->declared_type ? v_var->declared_type : TypeExpr::new_hole();
if (auto v_ident = v->as<ast_local_var>()->get_identifier()->try_as<ast_identifier>()) {
return create_new_local_variable(v->loc, v_ident->name, declared_type ? declared_type : TypeExpr::new_hole());
return create_new_local_variable(v->loc, v_ident->name, var_type, v_var->is_immutable);
} else {
return create_new_underscore_variable(v->loc, declared_type ? declared_type : TypeExpr::new_hole());
return create_new_underscore_variable(v->loc, var_type);
}
}
case ast_parenthesized_expr:
@ -588,7 +798,6 @@ static Expr* process_local_vars_lhs(AnyV v, CodeBlob& code) {
static blk_fl::val process_vertex(V<ast_local_vars_declaration> v, CodeBlob& code) {
Expr* x = process_local_vars_lhs(v->get_lhs(), code);
x->chk_lvalue();
Expr* y = process_expr(v->get_assigned_val(), code);
y->chk_rvalue();
x->predefine_vars();
@ -602,11 +811,83 @@ static blk_fl::val process_vertex(V<ast_local_vars_declaration> v, CodeBlob& cod
return blk_fl::end;
}
static bool is_expr_valid_as_return_self(Expr* return_expr) {
// `return self`
if (return_expr->cls == Expr::_Var && return_expr->val == 0) {
return true;
}
if (return_expr->cls == Expr::_ReturnSelf) {
return is_expr_valid_as_return_self(return_expr->args[1]);
}
if (return_expr->cls == Expr::_CondExpr) {
return is_expr_valid_as_return_self(return_expr->args[1]) && is_expr_valid_as_return_self(return_expr->args[2]);
}
return false;
}
// for mutating functions, having `return expr`, transform it to `return (modify_var1, ..., expr)`
static Expr* wrap_return_value_with_mutate_params(SrcLocation loc, CodeBlob& code, Expr* return_expr) {
Expr* tmp_var;
if (return_expr->cls != Expr::_Var) {
// `return complex_expr` - extract this into temporary variable (eval it before return)
// this is mandatory if it assigns to one of modified vars
tmp_var = create_new_underscore_variable(loc, return_expr->e_type);
tmp_var->predefine_vars();
tmp_var->define_new_vars(code);
Expr* assign_to_tmp_var = new Expr(Expr::_Letop, {tmp_var, return_expr});
assign_to_tmp_var->here = loc;
assign_to_tmp_var->flags = tmp_var->flags | Expr::_IsRvalue;
assign_to_tmp_var->deduce_type();
assign_to_tmp_var->pre_compile(code);
} else {
tmp_var = return_expr;
}
Expr* ret_tensor = new Expr(Expr::_Tensor, loc);
std::vector<TypeExpr*> type_list;
for (SymDef* p_sym: code.func_val->parameters) {
if (p_sym && dynamic_cast<SymValVariable*>(p_sym->value)->is_mutate_parameter()) {
Expr* p_expr = new Expr{Expr::_Var, p_sym->loc};
p_expr->sym = p_sym;
p_expr->val = p_sym->value->idx;
p_expr->flags = Expr::_IsRvalue;
p_expr->e_type = p_sym->value->get_type();
ret_tensor->pb_arg(p_expr);
type_list.emplace_back(p_expr->e_type);
}
}
ret_tensor->pb_arg(tmp_var);
type_list.emplace_back(tmp_var->e_type);
ret_tensor->flags = Expr::_IsRvalue;
ret_tensor->e_type = TypeExpr::new_tensor(std::move(type_list));
return ret_tensor;
}
static blk_fl::val process_vertex(V<ast_return_statement> v, CodeBlob& code) {
Expr* expr = process_expr(v->get_return_value(), code);
if (code.func_val->does_return_self()) {
if (!is_expr_valid_as_return_self(expr)) {
v->error("invalid return from `self` function");
}
Expr* var_self = new Expr(Expr::_Var, v->loc);
var_self->flags = Expr::_IsRvalue | Expr::_IsLvalue;
var_self->e_type = code.func_val->parameters[0]->value->get_type();
Expr* assign_to_self = new Expr(Expr::_Letop, {var_self, expr});
assign_to_self->here = v->loc;
assign_to_self->flags = Expr::_IsRvalue;
assign_to_self->deduce_type();
assign_to_self->pre_compile(code);
Expr* empty_tensor = new Expr(Expr::_Tensor, {});
empty_tensor->here = v->loc;
empty_tensor->flags = Expr::_IsRvalue;
empty_tensor->e_type = TypeExpr::new_tensor({});
expr = empty_tensor;
}
if (code.func_val->has_mutate_params()) {
expr = wrap_return_value_with_mutate_params(v->loc, code, expr);
}
expr->chk_rvalue();
try {
// std::cerr << "in return: ";
unify(expr->e_type, code.ret_type);
} catch (UnifyError& ue) {
std::ostringstream os;
@ -619,22 +900,29 @@ static blk_fl::val process_vertex(V<ast_return_statement> v, CodeBlob& code) {
return blk_fl::ret;
}
static void append_implicit_ret_stmt(V<ast_sequence> v, CodeBlob& code) {
TypeExpr* ret_type = TypeExpr::new_unit();
static void append_implicit_ret_stmt(SrcLocation loc_end, CodeBlob& code) {
Expr* expr = new Expr{Expr::_Tensor, {}};
expr->flags = Expr::_IsRvalue;
expr->here = loc_end;
expr->e_type = TypeExpr::new_unit();
if (code.func_val->does_return_self()) {
throw ParseError(loc_end, "missing return; forgot `return self`?");
}
if (code.func_val->has_mutate_params()) {
expr = wrap_return_value_with_mutate_params(loc_end, code, expr);
}
try {
// std::cerr << "in implicit return: ";
unify(ret_type, code.ret_type);
unify(expr->e_type, code.ret_type);
} catch (UnifyError& ue) {
std::ostringstream os;
os << "previous function return type " << code.ret_type
<< " cannot be unified with implicit end-of-block return type " << ret_type << ": " << ue;
throw ParseError(v->loc_end, os.str());
<< " cannot be unified with implicit end-of-block return type " << expr->e_type << ": " << ue;
throw ParseError(loc_end, os.str());
}
code.emplace_back(v->loc_end, Op::_Return);
std::vector<var_idx_t> tmp_vars = expr->pre_compile(code);
code.emplace_back(loc_end, Op::_Return, std::move(tmp_vars));
}
blk_fl::val process_statement(AnyV v, CodeBlob& code);
static blk_fl::val process_vertex(V<ast_sequence> v, CodeBlob& code, bool no_new_scope = false) {
if (!no_new_scope) {
open_scope(v->loc);
@ -792,7 +1080,7 @@ static blk_fl::val process_vertex(V<ast_assert_statement> v, CodeBlob& code) {
static Expr* process_catch_variable(AnyV catch_var, TypeExpr* var_type) {
if (auto v_ident = catch_var->try_as<ast_identifier>()) {
return create_new_local_variable(catch_var->loc, v_ident->name, var_type);
return create_new_local_variable(catch_var->loc, v_ident->name, var_type, true);
}
return create_new_underscore_variable(catch_var->loc, var_type);
}
@ -882,7 +1170,7 @@ blk_fl::val process_statement(AnyV v, CodeBlob& code) {
case ast_try_catch_statement:
return process_vertex(v->as<ast_try_catch_statement>(), code);
default: {
auto expr = process_expr(v, code);
Expr* expr = process_expr(v, code);
expr->chk_rvalue();
expr->pre_compile(code);
return blk_fl::end;
@ -890,18 +1178,16 @@ blk_fl::val process_statement(AnyV v, CodeBlob& code) {
}
}
static FormalArg process_vertex(V<ast_parameter> v, int fa_idx) {
if (v->get_identifier()->name.empty()) {
return std::make_tuple(v->param_type, (SymDef*)nullptr, v->loc);
static FormalArg process_vertex(V<ast_parameter> v, SymDef* param_sym) {
if (!param_sym) {
return std::make_tuple(v->param_type, nullptr, v->loc);
}
SymDef* new_sym_def = define_symbol(calc_sym_idx(v->get_identifier()->name), true, v->loc);
if (!new_sym_def) {
v->error("cannot define symbol");
if (!new_sym_def || new_sym_def->value) {
v->error("redefined parameter");
}
if (new_sym_def->value) {
v->error("redefined argument");
}
new_sym_def->value = new SymVal{SymValKind::_Param, fa_idx, v->param_type};
const SymValVariable* param_val = dynamic_cast<SymValVariable*>(param_sym->value);
new_sym_def->value = new SymValVariable(*param_val);
return std::make_tuple(v->param_type, new_sym_def, v->loc);
}
@ -911,13 +1197,13 @@ static void convert_function_body_to_CodeBlob(V<ast_function_declaration> v, V<a
tolk_assert(sym_val != nullptr);
open_scope(v->loc);
CodeBlob* blob = new CodeBlob{static_cast<std::string>(v->get_identifier()->name), v->loc, v->ret_type};
CodeBlob* blob = new CodeBlob{static_cast<std::string>(v->get_identifier()->name), v->loc, sym_val, v->ret_type};
if (v->marked_as_pure) {
blob->flags |= CodeBlob::_ForbidImpure;
}
FormalArgList legacy_arg_list;
for (int i = 0; i < v->get_num_params(); ++i) {
legacy_arg_list.emplace_back(process_vertex(v->get_param(i), i));
legacy_arg_list.emplace_back(process_vertex(v->get_param(i), sym_val->parameters[i]));
}
blob->import_params(std::move(legacy_arg_list));
@ -931,7 +1217,7 @@ static void convert_function_body_to_CodeBlob(V<ast_function_declaration> v, V<a
blk_fl::combine(res, process_statement(item, *blob));
}
if (res & blk_fl::end) {
append_implicit_ret_stmt(v_body, *blob);
append_implicit_ret_stmt(v_body->loc_end, *blob);
}
blob->close_blk(v_body->loc_end);

View file

@ -39,10 +39,10 @@ bool SymValCodeFunc::does_need_codegen() const {
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
// currently, there is no inlining, all functions are codegenerated
// (but actually, unused ones are later removed by Fift)
// in the future, we may want to implement a true AST inlining for "simple" functions
return true;
}
void SymValCodeFunc::set_code(CodeBlob* code) {

View file

@ -74,68 +74,6 @@ static td::RefInt256 calculate_method_id_by_func_name(std::string_view func_name
return td::make_refint((crc & 0xffff) | 0x10000);
}
static bool is_parameter_of_function(AnyV v_variable, V<ast_function_declaration> v_func) {
return v_variable->type == ast_identifier && v_func->get_param_list()->lookup_idx(v_variable->as<ast_identifier>()->name) != -1;
}
// 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(...)`
// todo this function (and optimization) was done before implementing AST, but after AST and registering symbols in advance,
// its behavior became a bit wrong: if anotherF is declared before f, than it's detected here, but still not inlined,
// since inlining is done is legacy code, using Expr
// in the future, inlining should be done on AST level, but it's impossible until all names resolving (including scopes)
// is also done on AST level
// in the future, when working on AST level, inlining should become much more powerful
// (for instance, it should inline `return anotherF(constants)`, etc.)
static bool detect_if_function_just_wraps_another(V<ast_function_declaration> v) {
if (v->method_id || v->marked_as_get_method || v->marked_as_builtin || v->marked_as_inline_ref || v->is_entrypoint) {
return false;
}
for (int i = 0; i < v->get_num_params(); ++i) {
if (v->get_param(i)->param_type->get_width() != 1 || v->get_param(i)->param_type->has_unknown_inside()) {
return false; // avoid situations like `f(int a, (int,int) b)`, inlining will be cumbersome
}
}
auto v_body = v->get_body()->try_as<ast_sequence>();
if (!v_body || v_body->size() != 1 || v_body->get_item(0)->type != ast_return_statement) {
return false;
}
auto v_return = v_body->get_item(0)->as<ast_return_statement>();
auto v_anotherF = v_return->get_return_value()->try_as<ast_function_call>();
if (!v_anotherF) {
return false;
}
V<ast_tensor> called_arg = v_anotherF->get_called_arg();
if (v_anotherF->get_called_f()->type != ast_identifier) {
return false;
}
std::string_view called_name = v_anotherF->get_called_f()->try_as<ast_identifier>()->name;
std::string_view function_name = v->get_identifier()->name;
const std::vector<AnyV>& v_arg_items = called_arg->get_items();
std::set<std::string_view> used_args;
for (AnyV v_arg : v_arg_items) {
if (!is_parameter_of_function(v_arg, v)) {
return false;
}
used_args.emplace(v_arg->as<ast_identifier>()->name);
}
if (static_cast<int>(used_args.size()) != v->get_num_params() || used_args.size() != v_arg_items.size()) {
return false;
}
// ok, f_current is a wrapper
if (G.is_verbosity(2)) {
std::cerr << function_name << " -> " << called_name << std::endl;
}
return true;
}
static void calc_arg_ret_order_of_asm_function(V<ast_asm_body> v_body, V<ast_parameter_list> param_list, TypeExpr* ret_type,
std::vector<int>& arg_order, std::vector<int>& ret_order) {
int cnt = param_list->size();
@ -201,7 +139,7 @@ static void register_constant(V<ast_constant_declaration> v) {
// todo currently, constant value calculation is dirty and roughly: init_value is evaluated to fif code
// and waited to be a single expression
// although it works, of course it should be later rewritten using AST calculations, as well as lots of other parts
CodeBlob code("tmp", v->loc, nullptr);
CodeBlob code("tmp", v->loc, nullptr, nullptr);
Expr* x = process_expr(init_value, code);
if (!x->is_rvalue()) {
v->get_init_value()->error("expression is not strictly Rvalue");
@ -211,9 +149,9 @@ static void register_constant(V<ast_constant_declaration> v) {
}
SymValConst* sym_val = nullptr;
if (x->cls == Expr::_Const) { // Integer constant
sym_val = new SymValConst{static_cast<int>(G.all_constants.size()), x->intval};
sym_val = new SymValConst(static_cast<int>(G.all_constants.size()), x->intval);
} else if (x->cls == Expr::_SliceConst) { // Slice constant (string)
sym_val = new SymValConst{static_cast<int>(G.all_constants.size()), x->strval};
sym_val = new SymValConst(static_cast<int>(G.all_constants.size()), x->strval);
} else if (x->cls == Expr::_Apply) { // even "1 + 2" is Expr::_Apply (it applies `_+_`)
code.emplace_back(v->loc, Op::_Import, std::vector<var_idx_t>());
auto tmp_vars = x->pre_compile(code);
@ -241,14 +179,14 @@ static void register_constant(V<ast_constant_declaration> v) {
if (op.origin.is_null() || !op.origin->is_valid()) {
init_value->error("precompiled expression did not result in a valid integer constant");
}
sym_val = new SymValConst{static_cast<int>(G.all_constants.size()), op.origin};
sym_val = new SymValConst(static_cast<int>(G.all_constants.size()), op.origin);
} else {
init_value->error("integer or slice literal or constant expected");
}
sym_def->value = sym_val;
#ifdef TOLK_DEBUG
dynamic_cast<SymValConst*>(sym_def->value)->name = v->get_identifier()->name;
sym_def->value->sym_name = v->get_identifier()->name;
#endif
G.all_constants.push_back(sym_def);
}
@ -259,35 +197,68 @@ static void register_global_var(V<ast_global_var_declaration> v) {
fire_error_redefinition_of_symbol(v->get_identifier(), sym_def);
}
sym_def->value = new SymValGlobVar{static_cast<int>(G.all_global_vars.size()), v->declared_type};
sym_def->value = new SymValGlobVar(static_cast<int>(G.all_global_vars.size()), v->declared_type);
#ifdef TOLK_DEBUG
dynamic_cast<SymValGlobVar*>(sym_def->value)->name = v->get_identifier()->name;
sym_def->value->sym_name = v->get_identifier()->name;
#endif
G.all_global_vars.push_back(sym_def);
}
static SymDef* register_parameter(V<ast_parameter> v, int idx) {
if (v->is_underscore()) {
return nullptr;
}
SymDef* sym_def = define_parameter(calc_sym_idx(v->get_identifier()->name), v->loc);
if (sym_def->value) {
// todo always false now, how to detect similar parameter names? (remember about underscore)
v->error("redefined parameter");
}
SymValVariable* sym_val = new SymValVariable(idx, v->param_type);
if (v->declared_as_mutate) {
sym_val->flags |= SymValVariable::flagMutateParameter;
}
if (!v->declared_as_mutate && idx == 0 && v->get_identifier()->name == "self") {
sym_val->flags |= SymValVariable::flagImmutable;
}
sym_def->value = sym_val;
#ifdef TOLK_DEBUG
sym_def->value->sym_name = v->get_identifier()->name;
#endif
return sym_def;
}
static void register_function(V<ast_function_declaration> v) {
std::string_view func_name = v->get_identifier()->name;
// calculate TypeExpr of a function: it's a map (args -> ret), probably surrounded by forall
TypeExpr* func_type = nullptr;
if (int n_args = v->get_num_params()) {
std::vector<TypeExpr*> arg_types;
arg_types.reserve(n_args);
for (int idx = 0; idx < n_args; ++idx) {
arg_types.emplace_back(v->get_param(idx)->param_type);
// calculate TypeExpr of a function: it's a map (params -> ret), probably surrounded by forall
TypeExpr* params_tensor_type = nullptr;
int n_params = v->get_num_params();
int n_mutate_params = 0;
std::vector<SymDef*> parameters_syms;
if (n_params) {
std::vector<TypeExpr*> param_tensor_items;
param_tensor_items.reserve(n_params);
parameters_syms.reserve(n_params);
for (int i = 0; i < n_params; ++i) {
auto v_param = v->get_param(i);
n_mutate_params += static_cast<int>(v_param->declared_as_mutate);
param_tensor_items.emplace_back(v_param->param_type);
parameters_syms.emplace_back(register_parameter(v_param, i));
}
func_type = TypeExpr::new_map(TypeExpr::new_tensor(std::move(arg_types)), v->ret_type);
params_tensor_type = TypeExpr::new_tensor(std::move(param_tensor_items));
} else {
func_type = TypeExpr::new_map(TypeExpr::new_unit(), v->ret_type);
params_tensor_type = TypeExpr::new_unit();
}
TypeExpr* function_type = TypeExpr::new_map(params_tensor_type, v->ret_type);
if (v->genericsT_list) {
std::vector<TypeExpr*> type_vars;
type_vars.reserve(v->genericsT_list->size());
for (int idx = 0; idx < v->genericsT_list->size(); ++idx) {
type_vars.emplace_back(v->genericsT_list->get_item(idx)->created_type);
}
func_type = TypeExpr::new_forall(std::move(type_vars), func_type);
function_type = TypeExpr::new_forall(std::move(type_vars), function_type);
}
if (v->marked_as_builtin) {
const SymDef* builtin_func = lookup_symbol(G.symbols.lookup(func_name));
@ -297,7 +268,7 @@ static void register_function(V<ast_function_declaration> v) {
}
#ifdef TOLK_DEBUG
// in release, we don't need this check, since `builtin` is used only in stdlib, which is our responsibility
if (!func_val->sym_type->equals_to(func_type) || func_val->is_marked_as_pure() != v->marked_as_pure) {
if (!func_val->sym_type->equals_to(function_type) || func_val->is_marked_as_pure() != v->marked_as_pure) {
v->error("declaration for `builtin` function doesn't match an actual one");
}
#endif
@ -309,7 +280,7 @@ static void register_function(V<ast_function_declaration> v) {
fire_error_redefinition_of_symbol(v->get_identifier(), sym_def);
}
if (G.is_verbosity(1)) {
std::cerr << "fun " << func_name << " : " << func_type << std::endl;
std::cerr << "fun " << func_name << " : " << function_type << std::endl;
}
if (v->marked_as_pure && v->ret_type->get_width() == 0) {
v->error("a pure function should return something, otherwise it will be optimized out anyway");
@ -317,11 +288,11 @@ static void register_function(V<ast_function_declaration> v) {
SymValFunc* sym_val = nullptr;
if (const auto* v_seq = v->get_body()->try_as<ast_sequence>()) {
sym_val = new SymValCodeFunc{static_cast<int>(G.all_code_functions.size()), func_type, v->marked_as_pure};
sym_val = new SymValCodeFunc(std::move(parameters_syms), static_cast<int>(G.all_code_functions.size()), function_type);
} else if (const auto* v_asm = v->get_body()->try_as<ast_asm_body>()) {
std::vector<int> arg_order, ret_order;
calc_arg_ret_order_of_asm_function(v_asm, v->get_param_list(), v->ret_type, arg_order, ret_order);
sym_val = new SymValAsmFunc{func_type, std::move(arg_order), std::move(ret_order), v->marked_as_pure};
sym_val = new SymValAsmFunc(std::move(parameters_syms), function_type, std::move(arg_order), std::move(ret_order), 0);
} else {
v->error("Unexpected function body statement");
}
@ -341,6 +312,9 @@ static void register_function(V<ast_function_declaration> v) {
} else if (v->is_entrypoint) {
sym_val->method_id = calculate_method_id_for_entrypoint(func_name);
}
if (v->marked_as_pure) {
sym_val->flags |= SymValFunc::flagMarkedAsPure;
}
if (v->marked_as_inline) {
sym_val->flags |= SymValFunc::flagInline;
}
@ -353,13 +327,19 @@ static void register_function(V<ast_function_declaration> v) {
if (v->is_entrypoint) {
sym_val->flags |= SymValFunc::flagIsEntrypoint;
}
if (detect_if_function_just_wraps_another(v)) {
sym_val->flags |= SymValFunc::flagWrapsAnotherF;
if (n_mutate_params) {
sym_val->flags |= SymValFunc::flagHasMutateParams;
}
if (v->accepts_self) {
sym_val->flags |= SymValFunc::flagAcceptsSelf;
}
if (v->returns_self) {
sym_val->flags |= SymValFunc::flagReturnsSelf;
}
sym_def->value = sym_val;
#ifdef TOLK_DEBUG
dynamic_cast<SymValFunc*>(sym_def->value)->name = func_name;
sym_def->value->sym_name = func_name;
#endif
if (dynamic_cast<SymValCodeFunc*>(sym_val)) {
G.all_code_functions.push_back(sym_def);

View file

@ -126,6 +126,18 @@ SymDef* define_global_symbol(sym_idx_t name_idx, SrcLocation loc) {
return registered; // registered->value is nullptr; it means, it's just created
}
SymDef* define_parameter(sym_idx_t name_idx, SrcLocation loc) {
// note, that parameters (defined at function declaration) are not inserted into symtable
// their SymDef is registered to be inserted into SymValFunc::parameters
// (and later ->value is filled with SymValVariable)
SymDef* registered = new SymDef(0, name_idx, loc);
#ifdef TOLK_DEBUG
registered->sym_name = registered->name();
#endif
return registered;
}
SymDef* define_symbol(sym_idx_t name_idx, bool force_new, SrcLocation loc) {
if (!name_idx) {
return nullptr;

View file

@ -17,6 +17,7 @@
#pragma once
#include "src-file.h"
#include "type-expr.h"
#include <functional>
#include <memory>
@ -25,14 +26,23 @@ namespace tolk {
typedef int var_idx_t;
typedef int sym_idx_t;
enum class SymValKind { _Param, _Var, _Func, _Typename, _GlobVar, _Const };
enum class SymValKind { _Var, _Func, _GlobVar, _Const };
struct SymValBase {
SymValKind kind;
int idx;
SymValBase(SymValKind kind, int idx) : kind(kind), idx(idx) {
TypeExpr* sym_type;
#ifdef TOLK_DEBUG
std::string sym_name; // seeing symbol name in debugger makes it much easier to delve into Tolk sources
#endif
SymValBase(SymValKind kind, int idx, TypeExpr* sym_type) : kind(kind), idx(idx), sym_type(sym_type) {
}
virtual ~SymValBase() = default;
TypeExpr* get_type() const {
return sym_type;
}
};
@ -98,6 +108,7 @@ void close_scope();
SymDef* lookup_symbol(sym_idx_t idx);
SymDef* define_global_symbol(sym_idx_t name_idx, SrcLocation loc = {});
SymDef* define_parameter(sym_idx_t name_idx, SrcLocation loc);
SymDef* define_symbol(sym_idx_t name_idx, bool force_new, SrcLocation loc);
} // namespace tolk

View file

@ -69,13 +69,14 @@ using const_idx_t = int;
struct TmpVar {
TypeExpr* v_type;
var_idx_t idx;
bool is_tmp_unnamed;
sym_idx_t name;
sym_idx_t sym_idx;
int coord;
SrcLocation where;
std::vector<std::function<void(SrcLocation)>> on_modification;
TmpVar(var_idx_t _idx, bool _is_tmp_unnamed, TypeExpr* _type, SymDef* sym, SrcLocation loc);
TmpVar(var_idx_t _idx, TypeExpr* _type, sym_idx_t sym_idx, SrcLocation loc);
bool is_unnamed() const { return sym_idx == 0; }
void show(std::ostream& os, int omit_idx = 0) const;
void dump(std::ostream& os) const;
void set_location(SrcLocation loc);
@ -401,40 +402,56 @@ struct AsmOpList;
*
*/
struct SymVal : SymValBase {
TypeExpr* sym_type;
SymVal(SymValKind kind, int idx, TypeExpr* sym_type = nullptr)
: SymValBase(kind, idx), sym_type(sym_type) {
struct SymValVariable : SymValBase {
enum SymValFlag {
flagMutateParameter = 1, // parameter was declared with `mutate` keyword
flagImmutable = 2, // variable was declared via `val` (not `var`)
};
int flags{0};
~SymValVariable() override = default;
SymValVariable(int val, TypeExpr* sym_type)
: SymValBase(SymValKind::_Var, val, sym_type) {}
bool is_function_parameter() const {
return idx >= 0;
}
~SymVal() override = default;
TypeExpr* get_type() const {
return sym_type;
bool is_mutate_parameter() const {
return flags & flagMutateParameter;
}
bool is_local_var() const {
return idx == -1;
}
bool is_immutable() const {
return flags & flagImmutable;
}
};
struct SymValFunc : SymVal {
struct SymValFunc : SymValBase {
enum SymValFlag {
flagInline = 1, // marked `@inline`
flagInlineRef = 2, // marked `@inline_ref`
flagWrapsAnotherF = 4, // `fun thisF(...args) { return anotherF(...args); }` (calls to thisF will be inlined)
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 func(): T`, method_id is auto-assigned
flagIsEntrypoint = 128, // it's `main` / `onExternalMessage` / etc.
flagHasMutateParams = 256, // has parameters declared as `mutate`
flagAcceptsSelf = 512, // is a member function (has `self` first parameter)
flagReturnsSelf = 1024, // return type is `self` (returns the mutated 1st argument), calls can be chainable
};
td::RefInt256 method_id; // todo why int256? it's small
int flags{0};
std::vector<SymDef*> parameters; // [i]-th may be nullptr for underscore; if not, its val is SymValVariable
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 marked_as_pure)
: SymVal(SymValKind::_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(SymValKind::_Func, val, _ft), flags(marked_as_pure ? flagMarkedAsPure : 0), arg_order(_arg_order), ret_order(_ret_order) {
SymValFunc(std::vector<SymDef*> parameters, int val, TypeExpr* sym_type, int flags)
: SymValBase(SymValKind::_Func, val, sym_type), flags(flags), parameters(std::move(parameters)) {
}
SymValFunc(std::vector<SymDef*> parameters, int val, TypeExpr* sym_type, int flags, std::initializer_list<int> arg_order, std::initializer_list<int> ret_order)
: SymValBase(SymValKind::_Func, val, sym_type), flags(flags), parameters(std::move(parameters)), arg_order(arg_order), ret_order(ret_order) {
}
const std::vector<int>* get_arg_order() const {
@ -450,9 +467,6 @@ struct SymValFunc : SymVal {
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;
}
@ -465,32 +479,35 @@ struct SymValFunc : SymVal {
bool is_entrypoint() const {
return flags & flagIsEntrypoint;
}
bool has_mutate_params() const {
return flags & flagHasMutateParams;
}
bool does_accept_self() const {
return flags & flagAcceptsSelf;
}
bool does_return_self() const {
return flags & flagReturnsSelf;
}
};
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 marked_as_pure) : SymValFunc(val, _ft, marked_as_pure), code(nullptr) {
SymValCodeFunc(std::vector<SymDef*> parameters, int val, TypeExpr* _ft)
: SymValFunc(std::move(parameters), val, _ft, 0), code(nullptr) {
}
bool does_need_codegen() const;
void set_code(CodeBlob* code);
};
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(SymValKind::_GlobVar, val), sym_type(gvtype), out_idx(oidx) {
SymValGlobVar(int val, TypeExpr* gvtype)
: SymValBase(SymValKind::_GlobVar, val, gvtype) {
}
~SymValGlobVar() override = default;
TypeExpr* get_type() const {
return sym_type;
}
};
struct SymValConst : SymValBase {
@ -499,14 +516,12 @@ struct SymValConst : SymValBase {
td::RefInt256 intval;
std::string strval;
ConstKind kind;
#ifdef TOLK_DEBUG
std::string name; // seeing const name in debugger makes it much easier to delve into Tolk sources
#endif
SymValConst(int idx, td::RefInt256 value)
: SymValBase(SymValKind::_Const, idx), intval(value), kind(IntConst) {
: SymValBase(SymValKind::_Const, idx, TypeExpr::new_atomic(TypeExpr::_Int)), intval(std::move(value)), kind(IntConst) {
}
SymValConst(int idx, std::string value)
: SymValBase(SymValKind::_Const, idx), strval(value), kind(SliceConst) {
: SymValBase(SymValKind::_Const, idx, TypeExpr::new_atomic(TypeExpr::_Slice)), strval(std::move(value)), kind(SliceConst) {
}
~SymValConst() override = default;
td::RefInt256 get_int_value() const {
@ -529,9 +544,10 @@ struct SymValConst : SymValBase {
struct Expr {
enum ExprCls {
_None,
_Apply,
_VarApply,
_GrabMutatedVars,
_ReturnSelf,
_MkTuple,
_Tensor,
_Const,
@ -539,14 +555,13 @@ struct Expr {
_GlobFunc,
_GlobVar,
_Letop,
_LetFirst,
_Hole,
_CondExpr,
_SliceConst,
};
ExprCls cls;
int val{0};
enum { _IsRvalue = 2, _IsLvalue = 4, _IsImpure = 32 };
enum { _IsRvalue = 2, _IsLvalue = 4, _IsImmutable = 8, _IsImpure = 32 };
int flags{0};
SrcLocation here;
td::RefInt256 intval;
@ -554,8 +569,6 @@ struct Expr {
SymDef* sym{nullptr};
TypeExpr* e_type{nullptr};
std::vector<Expr*> args;
explicit Expr(ExprCls c = _None) : cls(c) {
}
Expr(ExprCls c, SrcLocation loc) : cls(c), here(loc) {
}
Expr(ExprCls c, std::vector<Expr*> _args) : cls(c), args(std::move(_args)) {
@ -585,33 +598,38 @@ struct Expr {
bool is_lvalue() const {
return flags & _IsLvalue;
}
bool is_immutable() const {
return flags & _IsImmutable;
}
bool is_mktuple() const {
return cls == _MkTuple;
}
void chk_rvalue() const {
if (!is_rvalue()) {
throw ParseError(here, "rvalue expected");
fire_error_rvalue_expected();
}
}
void chk_lvalue() const {
if (!is_lvalue()) {
throw ParseError(here, "lvalue expected");
}
}
bool deduce_type();
void deduce_type();
void set_location(SrcLocation loc) {
here = loc;
}
SrcLocation get_location() const {
return here;
}
int define_new_vars(CodeBlob& code);
int predefine_vars();
void define_new_vars(CodeBlob& code);
void predefine_vars();
std::vector<var_idx_t> pre_compile(CodeBlob& code, std::vector<std::pair<SymDef*, var_idx_t>>* lval_globs = nullptr) const;
var_idx_t new_tmp(CodeBlob& code) const;
std::vector<var_idx_t> new_tmp_vect(CodeBlob& code) const {
return {new_tmp(code)};
}
GNU_ATTRIBUTE_COLD GNU_ATTRIBUTE_NORETURN
void fire_error_rvalue_expected() const;
GNU_ATTRIBUTE_COLD GNU_ATTRIBUTE_NORETURN
void fire_error_lvalue_expected(const std::string& details) const;
GNU_ATTRIBUTE_COLD GNU_ATTRIBUTE_NORETURN
void fire_error_modifying_immutable(const std::string& details) const;
};
/*
@ -1324,24 +1342,17 @@ struct SymValAsmFunc : SymValFunc {
simple_compile_func_t simple_compile;
compile_func_t ext_compile;
~SymValAsmFunc() override = default;
SymValAsmFunc(TypeExpr* ft, std::vector<int>&& arg_order, std::vector<int>&& ret_order, bool marked_as_pure)
: SymValFunc(-1, ft, marked_as_pure) {
SymValAsmFunc(std::vector<SymDef*> parameters, TypeExpr* ft, std::vector<int>&& arg_order, std::vector<int>&& ret_order, int flags)
: SymValFunc(std::move(parameters), -1, ft, flags) {
this->arg_order = std::move(arg_order);
this->ret_order = std::move(ret_order);
}
SymValAsmFunc(TypeExpr* ft, simple_compile_func_t _compile, bool marked_as_pure)
: SymValFunc(-1, ft, marked_as_pure), simple_compile(std::move(_compile)) {
SymValAsmFunc(std::vector<SymDef*> parameters, TypeExpr* ft, simple_compile_func_t _compile, int flags)
: SymValFunc(std::move(parameters), -1, ft, flags), simple_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 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 marked_as_pure = false)
: SymValFunc(-1, ft, arg_order, ret_order, marked_as_pure), ext_compile(std::move(_compile)) {
SymValAsmFunc(std::vector<SymDef*> parameters, TypeExpr* ft, simple_compile_func_t _compile, int flags,
std::initializer_list<int> arg_order, std::initializer_list<int> ret_order)
: SymValFunc(std::move(parameters), -1, ft, flags, arg_order, ret_order), simple_compile(std::move(_compile)) {
}
void set_code(std::vector<AsmOp> code);
bool compile(AsmOpList& dest, std::vector<VarDescr>& out, std::vector<VarDescr>& in, SrcLocation where) const;
@ -1349,29 +1360,32 @@ struct SymValAsmFunc : SymValFunc {
struct CodeBlob {
enum { _ForbidImpure = 4 };
int var_cnt, in_var_cnt, op_cnt;
int var_cnt, in_var_cnt;
TypeExpr* ret_type;
const SymValCodeFunc* func_val;
std::string name;
SrcLocation loc;
std::vector<TmpVar> vars;
std::unique_ptr<Op> ops;
std::unique_ptr<Op>* cur_ops;
std::vector<Op*> debug_ttt;
std::stack<std::unique_ptr<Op>*> cur_ops_stack;
int flags = 0;
bool require_callxargs = false;
CodeBlob(std::string name, SrcLocation loc, TypeExpr* ret)
: var_cnt(0), in_var_cnt(0), op_cnt(0), ret_type(ret), name(std::move(name)), loc(loc), cur_ops(&ops) {
CodeBlob(std::string name, SrcLocation loc, const SymValCodeFunc* func_val, TypeExpr* ret_type)
: var_cnt(0), in_var_cnt(0), ret_type(ret_type), func_val(func_val), name(std::move(name)), loc(loc), cur_ops(&ops) {
}
template <typename... Args>
Op& emplace_back(Args&&... args) {
Op& res = *(*cur_ops = std::make_unique<Op>(args...));
cur_ops = &(res.next);
debug_ttt.push_back(&res);
return res;
}
bool import_params(FormalArgList arg_list);
var_idx_t create_var(bool is_tmp_unnamed, TypeExpr* var_type, SymDef* sym, SrcLocation loc);
var_idx_t create_var(TypeExpr* var_type, var_idx_t sym_idx, SrcLocation loc);
var_idx_t create_tmp_var(TypeExpr* var_type, SrcLocation loc) {
return create_var(true, var_type, nullptr, loc);
return create_var(var_type, 0, loc);
}
int split_vars(bool strict = false);
bool compute_used_code_vars();

View file

@ -2,28 +2,20 @@
#include <vector>
#include <iostream>
#include "lexer.h"
namespace tolk {
struct TypeExpr {
enum Kind { te_Unknown, te_Var, te_Indirect, te_Atomic, te_Tensor, te_Tuple, te_Map, te_ForAll };
// todo not _
enum AtomicType {
_Int = tok_int,
_Cell = tok_cell,
_Slice = tok_slice,
_Builder = tok_builder,
_Cont = tok_continuation,
_Tuple = tok_tuple,
};
enum AtomicType { _Int, _Cell, _Slice, _Builder, _Continutaion, _Tuple };
Kind constr;
int value;
int minw, maxw;
static constexpr int w_inf = 1023;
std::vector<TypeExpr*> args;
bool was_forall_var = false;
TypeExpr(Kind _constr, int _val = 0) : constr(_constr), value(_val), minw(0), maxw(w_inf) {
explicit TypeExpr(Kind _constr, int _val = 0) : constr(_constr), value(_val), minw(0), maxw(w_inf) {
}
TypeExpr(Kind _constr, int _val, int width) : constr(_constr), value(_val), minw(width), maxw(width) {
}
@ -48,6 +40,7 @@ struct TypeExpr {
args.insert(args.end(), list.begin(), list.end());
compute_width();
}
bool is_atomic() const {
return constr == te_Atomic;
}
@ -127,9 +120,7 @@ struct TypeExpr {
static TypeExpr* new_forall(std::vector<TypeExpr*> list, TypeExpr* body) {
return new TypeExpr{te_ForAll, body, std::move(list)};
}
static TypeExpr* new_forall(std::initializer_list<TypeExpr*> list, TypeExpr* body) {
return new TypeExpr{te_ForAll, body, std::move(list)};
}
static bool remove_indirect(TypeExpr*& te, TypeExpr* forbidden = nullptr);
static std::vector<TypeExpr*> remove_forall(TypeExpr*& te);
static bool remove_forall_in(TypeExpr*& te, TypeExpr* te2, const std::vector<TypeExpr*>& new_vars);

View file

@ -264,7 +264,7 @@ std::ostream& TypeExpr::print(std::ostream& os, int lex_level) const {
return os << "slice";
case _Builder:
return os << "builder";
case _Cont:
case _Continutaion:
return os << "cont";
case _Tuple:
return os << "tuple";