Merge pull request #147 from aeternity/bytes-concat
Bytes.concat and Bytes.split
This commit is contained in:
commit
92ac8b1f02
@ -2,7 +2,7 @@
|
||||
|
||||
{erl_opts, [debug_info]}.
|
||||
|
||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"3106ca1"}}}
|
||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"17c9656"}}}
|
||||
, {getopt, "1.0.1"}
|
||||
, {eblake2, "1.0.0"}
|
||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{"1.1.0",
|
||||
[{<<"aebytecode">>,
|
||||
{git,"https://github.com/aeternity/aebytecode.git",
|
||||
{ref,"3106ca13063cf12f23e459cf60c6f987777a7e83"}},
|
||||
{ref,"17c9656f5ca60f6522d598aaf2999660030f8664"}},
|
||||
0},
|
||||
{<<"aeserialization">>,
|
||||
{git,"https://github.com/aeternity/aeserialization.git",
|
||||
|
@ -81,7 +81,9 @@
|
||||
-type type() :: aeso_syntax:type().
|
||||
-type name() :: string().
|
||||
-type qname() :: [string()].
|
||||
-type typesig() :: {type_sig, aeso_syntax:ann(), [aeso_syntax:named_arg_t()], [type()], type()}.
|
||||
-type typesig() :: {type_sig, aeso_syntax:ann(), type_constraints(), [aeso_syntax:named_arg_t()], [type()], type()}.
|
||||
|
||||
-type type_constraints() :: none | bytes_concat | bytes_split.
|
||||
|
||||
-type fun_info() :: {aeso_syntax:ann(), typesig() | type()}.
|
||||
-type type_info() :: {aeso_syntax:ann(), typedef()}.
|
||||
@ -206,7 +208,7 @@ bind_state(Env) ->
|
||||
false -> Unit
|
||||
end,
|
||||
Env1 = bind_funs([{"state", State},
|
||||
{"put", {type_sig, [stateful | Ann], [], [State], Unit}}], Env),
|
||||
{"put", {type_sig, [stateful | Ann], none, [], [State], Unit}}], Env),
|
||||
|
||||
case lookup_type(Env, {id, Ann, "event"}) of
|
||||
{E, _} ->
|
||||
@ -370,16 +372,17 @@ global_env() ->
|
||||
Option = fun(T) -> {app_t, Ann, {id, Ann, "option"}, [T]} end,
|
||||
Map = fun(A, B) -> {app_t, Ann, {id, Ann, "map"}, [A, B]} end,
|
||||
Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end,
|
||||
Fun = fun(Ts, T) -> {type_sig, Ann, [], Ts, T} end,
|
||||
FunC = fun(C, Ts, T) -> {type_sig, Ann, C, [], Ts, T} end,
|
||||
Fun = fun(Ts, T) -> FunC(none, Ts, T) end,
|
||||
Fun1 = fun(S, T) -> Fun([S], T) end,
|
||||
%% Lambda = fun(Ts, T) -> {fun_t, Ann, [], Ts, T} end,
|
||||
%% Lambda1 = fun(S, T) -> Lambda([S], T) end,
|
||||
StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [], Ts, T} end,
|
||||
StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], none, [], Ts, T} end,
|
||||
TVar = fun(X) -> {tvar, Ann, "'" ++ X} end,
|
||||
SignId = {id, Ann, "signature"},
|
||||
SignDef = {bytes, Ann, <<0:64/unit:8>>},
|
||||
Signature = {named_arg_t, Ann, SignId, SignId, {typed, Ann, SignDef, SignId}},
|
||||
SignFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [Signature], Ts, T} end,
|
||||
SignFun = fun(Ts, T) -> {type_sig, [stateful|Ann], none, [Signature], Ts, T} end,
|
||||
TTL = {qid, Ann, ["Chain", "ttl"]},
|
||||
Fee = Int,
|
||||
[A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]),
|
||||
@ -508,7 +511,10 @@ global_env() ->
|
||||
BytesScope = #scope
|
||||
{ funs = MkDefs(
|
||||
[{"to_int", Fun1(Bytes(any), Int)},
|
||||
{"to_str", Fun1(Bytes(any), String)}]) },
|
||||
{"to_str", Fun1(Bytes(any), String)},
|
||||
{"concat", FunC(bytes_concat, [Bytes(any), Bytes(any)], Bytes(any))},
|
||||
{"split", FunC(bytes_split, [Bytes(any)], Pair(Bytes(any), Bytes(any)))}
|
||||
]) },
|
||||
|
||||
%% Conversion
|
||||
IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) },
|
||||
@ -669,7 +675,7 @@ check_typedef_sccs(Env, TypeMap, [{acyclic, Name} | SCCs], Acc) ->
|
||||
check_typedef_sccs(Env2, TypeMap, SCCs, Acc1);
|
||||
{variant_t, Cons} ->
|
||||
Target = check_type(Env1, app_t(Ann, D, Xs)),
|
||||
ConType = fun([]) -> Target; (Args) -> {type_sig, Ann, [], Args, Target} end,
|
||||
ConType = fun([]) -> Target; (Args) -> {type_sig, Ann, none, [], Args, Target} end,
|
||||
ConTypes = [ begin
|
||||
{constr_t, _, {con, _, Con}, Args} = ConDef,
|
||||
{Con, ConType(Args)}
|
||||
@ -842,7 +848,7 @@ check_constructor_overlap(Env, Con = {con, Ann, Name}, NewType) ->
|
||||
case lookup_env(Env, term, Ann, Name) of
|
||||
false -> ok;
|
||||
{_, {Ann, Type}} ->
|
||||
OldType = case Type of {type_sig, _, _, _, T} -> T;
|
||||
OldType = case Type of {type_sig, _, _, _, _, T} -> T;
|
||||
_ -> Type end,
|
||||
OldCon = {con, Ann, Name},
|
||||
type_error({repeated_constructor, [{OldCon, OldType}, {Con, NewType}]})
|
||||
@ -884,16 +890,15 @@ check_reserved_entrypoints(Funs) ->
|
||||
-spec check_fundecl(env(), aeso_syntax:decl()) -> {{name(), typesig()}, aeso_syntax:decl()}.
|
||||
check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type = {fun_t, _, _, _, _}}) ->
|
||||
Type1 = {fun_t, _, Named, Args, Ret} = check_type(Env, Type),
|
||||
{{Name, {type_sig, Ann, Named, Args, Ret}}, {fun_decl, Ann, Id, Type1}};
|
||||
{{Name, {type_sig, Ann, none, Named, Args, Ret}}, {fun_decl, Ann, Id, Type1}};
|
||||
check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type}) ->
|
||||
type_error({fundecl_must_have_funtype, Ann, Id, Type}),
|
||||
{{Name, {type_sig, Ann, [], [], Type}}, check_type(Env, Type)}.
|
||||
{{Name, {type_sig, Ann, none, [], [], Type}}, check_type(Env, Type)}.
|
||||
|
||||
infer_nonrec(Env, LetFun) ->
|
||||
create_constraints(),
|
||||
NewLetFun = infer_letfun(Env, LetFun),
|
||||
check_special_funs(Env, NewLetFun),
|
||||
solve_constraints(Env),
|
||||
destroy_and_report_unsolved_constraints(Env),
|
||||
Result = {TypeSig, _} = instantiate(NewLetFun),
|
||||
print_typesig(TypeSig),
|
||||
@ -901,7 +906,7 @@ infer_nonrec(Env, LetFun) ->
|
||||
|
||||
%% Currenty only the init function.
|
||||
check_special_funs(Env, {{"init", Type}, _}) ->
|
||||
{type_sig, Ann, _Named, _Args, Res} = Type,
|
||||
{type_sig, Ann, _Constr, _Named, _Args, Res} = Type,
|
||||
State =
|
||||
%% We might have implicit (no) state.
|
||||
case lookup_type(Env, {id, [], "state"}) of
|
||||
@ -911,7 +916,8 @@ check_special_funs(Env, {{"init", Type}, _}) ->
|
||||
unify(Env, Res, State, {checking_init_type, Ann});
|
||||
check_special_funs(_, _) -> ok.
|
||||
|
||||
typesig_to_fun_t({type_sig, Ann, Named, Args, Res}) -> {fun_t, Ann, Named, Args, Res}.
|
||||
typesig_to_fun_t({type_sig, Ann, _Constr, Named, Args, Res}) ->
|
||||
{fun_t, Ann, Named, Args, Res}.
|
||||
|
||||
infer_letrec(Env, Defs) ->
|
||||
create_constraints(),
|
||||
@ -939,13 +945,13 @@ infer_letfun(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Bo
|
||||
Env = Env0#env{ stateful = aeso_syntax:get_ann(stateful, Attrib, false),
|
||||
current_function = Fun },
|
||||
check_unique_arg_names(Fun, Args),
|
||||
ArgTypes = [{ArgName, check_type(Env, arg_type(T))} || {arg, _, ArgName, T} <- Args],
|
||||
ExpectedType = check_type(Env, arg_type(What)),
|
||||
ArgTypes = [{ArgName, check_type(Env, arg_type(ArgAnn, T))} || {arg, ArgAnn, ArgName, T} <- Args],
|
||||
ExpectedType = check_type(Env, arg_type(NameAttrib, What)),
|
||||
NewBody={typed, _, _, ResultType} = check_expr(bind_vars(ArgTypes, Env), Body, ExpectedType),
|
||||
NewArgs = [{arg, A1, {id, A2, ArgName}, T}
|
||||
|| {{_, T}, {arg, A1, {id, A2, ArgName}, _}} <- lists:zip(ArgTypes, Args)],
|
||||
NamedArgs = [],
|
||||
TypeSig = {type_sig, Attrib, NamedArgs, [T || {arg, _, _, T} <- NewArgs], ResultType},
|
||||
TypeSig = {type_sig, Attrib, none, NamedArgs, [T || {arg, _, _, T} <- NewArgs], ResultType},
|
||||
{{Name, TypeSig},
|
||||
{letfun, Attrib, {id, NameAttrib, Name}, NewArgs, ResultType, NewBody}}.
|
||||
|
||||
@ -959,11 +965,14 @@ check_unique_arg_names(Fun, Args) ->
|
||||
print_typesig({Name, TypeSig}) ->
|
||||
?PRINT_TYPES("Inferred ~s : ~s\n", [Name, pp(TypeSig)]).
|
||||
|
||||
arg_type({id, Attrs, "_"}) ->
|
||||
fresh_uvar(Attrs);
|
||||
arg_type({app_t, Attrs, Name, Args}) ->
|
||||
{app_t, Attrs, Name, [arg_type(T) || T <- Args]};
|
||||
arg_type(T) ->
|
||||
arg_type(ArgAnn, {id, Ann, "_"}) ->
|
||||
case aeso_syntax:get_ann(origin, Ann, user) of
|
||||
system -> fresh_uvar(ArgAnn);
|
||||
user -> fresh_uvar(Ann)
|
||||
end;
|
||||
arg_type(ArgAnn, {app_t, Attrs, Name, Args}) ->
|
||||
{app_t, Attrs, Name, [arg_type(ArgAnn, T) || T <- Args]};
|
||||
arg_type(_, T) ->
|
||||
T.
|
||||
|
||||
app_t(_Ann, Name, []) -> Name;
|
||||
@ -981,14 +990,14 @@ lookup_name(Env, As, Id, Options) ->
|
||||
Freshen = proplists:get_value(freshen, Options, false),
|
||||
check_stateful(Env, Id, Ty),
|
||||
Ty1 = case Ty of
|
||||
{type_sig, _, _, _, _} -> freshen_type(As, typesig_to_fun_t(Ty));
|
||||
_ when Freshen -> freshen_type(As, Ty);
|
||||
_ -> Ty
|
||||
{type_sig, _, _, _, _, _} -> freshen_type_sig(As, Ty);
|
||||
_ when Freshen -> freshen_type(As, Ty);
|
||||
_ -> Ty
|
||||
end,
|
||||
{set_qname(QId, Id), Ty1}
|
||||
end.
|
||||
|
||||
check_stateful(#env{ stateful = false, current_function = Fun }, Id, Type = {type_sig, _, _, _, _}) ->
|
||||
check_stateful(#env{ stateful = false, current_function = Fun }, Id, Type = {type_sig, _, _, _, _, _}) ->
|
||||
case aeso_syntax:get_ann(stateful, Type, false) of
|
||||
false -> ok;
|
||||
true ->
|
||||
@ -1115,7 +1124,7 @@ infer_expr(Env, {list_comp, AttrsL, Yield, [{comprehension_if, AttrsIF, Cond}|Re
|
||||
, {list_comp, AttrsL, TypedYield, [{comprehension_if, AttrsIF, NewCond}|TypedRest]}
|
||||
, ResType};
|
||||
infer_expr(Env, {list_comp, AsLC, Yield, [{letval, AsLV, Pattern, Type, E}|Rest]}) ->
|
||||
NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, AsLV, E, arg_type(Type)}),
|
||||
NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, AsLV, E, arg_type(AsLV, Type)}),
|
||||
BlockType = fresh_uvar(AsLV),
|
||||
{'case', _, NewPattern, NewRest} =
|
||||
infer_case( Env
|
||||
@ -1132,7 +1141,7 @@ infer_expr(Env, {list_comp, AsLC, Yield, [{letval, AsLV, Pattern, Type, E}|Rest]
|
||||
};
|
||||
infer_expr(Env, {list_comp, AsLC, Yield, [Def={letfun, AsLF, _, _, _, _}|Rest]}) ->
|
||||
{{Name, TypeSig}, LetFun} = infer_letfun(Env, Def),
|
||||
FunT = freshen_type(AsLF, typesig_to_fun_t(TypeSig)),
|
||||
FunT = typesig_to_fun_t(TypeSig),
|
||||
NewE = bind_var({id, AsLF, Name}, FunT, Env),
|
||||
{typed, _, {list_comp, _, TypedYield, TypedRest}, ResType} =
|
||||
infer_expr(NewE, {list_comp, AsLC, Yield, Rest}),
|
||||
@ -1339,7 +1348,7 @@ infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _}|Rest], BlockType) ->
|
||||
NewE = bind_var({id, Ann, Name}, FunT, Env),
|
||||
[LetFun|infer_block(NewE, Attrs, Rest, BlockType)];
|
||||
infer_block(Env, _, [{letval, Attrs, Pattern, Type, E}|Rest], BlockType) ->
|
||||
NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, Attrs, E, arg_type(Type)}),
|
||||
NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, Attrs, E, arg_type(aeso_syntax:get_ann(Pattern), Type)}),
|
||||
{'case', _, NewPattern, {typed, _, {block, _, NewRest}, _}} =
|
||||
infer_case(Env, Attrs, Pattern, PatType, {block, Attrs, Rest}, BlockType),
|
||||
[{letval, Attrs, NewPattern, Type, NewE}|NewRest];
|
||||
@ -1481,12 +1490,10 @@ create_constraints() ->
|
||||
create_bytes_constraints(),
|
||||
create_field_constraints().
|
||||
|
||||
solve_constraints(Env) ->
|
||||
destroy_and_report_unsolved_constraints(Env) ->
|
||||
solve_named_argument_constraints(Env),
|
||||
solve_bytes_constraints(Env),
|
||||
solve_field_constraints(Env).
|
||||
|
||||
destroy_and_report_unsolved_constraints(Env) ->
|
||||
solve_field_constraints(Env),
|
||||
destroy_and_report_unsolved_field_constraints(Env),
|
||||
destroy_and_report_unsolved_bytes_constraints(Env),
|
||||
destroy_and_report_unsolved_named_argument_constraints(Env).
|
||||
@ -1539,7 +1546,8 @@ destroy_and_report_unsolved_named_argument_constraints(Env) ->
|
||||
|
||||
%% -- Bytes constraints --
|
||||
|
||||
-type byte_constraint() :: {is_bytes, utype()}.
|
||||
-type byte_constraint() :: {is_bytes, utype()}
|
||||
| {add_bytes, aeso_syntax:ann(), concat | split, utype(), utype(), utype()}.
|
||||
|
||||
create_bytes_constraints() ->
|
||||
ets_new(bytes_constraints, [bag]).
|
||||
@ -1551,21 +1559,52 @@ get_bytes_constraints() ->
|
||||
add_bytes_constraint(Constraint) ->
|
||||
ets_insert(bytes_constraints, Constraint).
|
||||
|
||||
solve_bytes_constraints(_Env) ->
|
||||
solve_bytes_constraints(Env) ->
|
||||
[ solve_bytes_constraint(Env, C) || C <- get_bytes_constraints() ],
|
||||
ok.
|
||||
|
||||
solve_bytes_constraint(_Env, {is_bytes, _}) -> ok;
|
||||
solve_bytes_constraint(Env, {add_bytes, Ann, _, A0, B0, C0}) ->
|
||||
A = unfold_types_in_type(Env, dereference(A0)),
|
||||
B = unfold_types_in_type(Env, dereference(B0)),
|
||||
C = unfold_types_in_type(Env, dereference(C0)),
|
||||
case {A, B, C} of
|
||||
{{bytes_t, _, M}, {bytes_t, _, N}, _} -> unify(Env, {bytes_t, Ann, M + N}, C, {at, Ann});
|
||||
{{bytes_t, _, M}, _, {bytes_t, _, R}} when R >= M -> unify(Env, {bytes_t, Ann, R - M}, B, {at, Ann});
|
||||
{_, {bytes_t, _, N}, {bytes_t, _, R}} when R >= N -> unify(Env, {bytes_t, Ann, R - N}, A, {at, Ann});
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
destroy_bytes_constraints() ->
|
||||
ets_delete(bytes_constraints).
|
||||
|
||||
destroy_and_report_unsolved_bytes_constraints(Env) ->
|
||||
[ check_bytes_constraint(Env, C) || C <- get_bytes_constraints() ],
|
||||
Constraints = get_bytes_constraints(),
|
||||
InAddConstraint = [ T || {add_bytes, _, _, A, B, C} <- Constraints,
|
||||
T <- [A, B, C],
|
||||
element(1, T) /= bytes_t ],
|
||||
%% Skip is_bytes constraints for types that occur in add_bytes constraints
|
||||
%% (no need to generate error messages for both is_bytes and add_bytes).
|
||||
Skip = fun({is_bytes, T}) -> lists:member(T, InAddConstraint);
|
||||
(_) -> false end,
|
||||
[ check_bytes_constraint(Env, C) || C <- Constraints, not Skip(C) ],
|
||||
destroy_bytes_constraints().
|
||||
|
||||
check_bytes_constraint(Env, {is_bytes, Type}) ->
|
||||
Type1 = unfold_types_in_type(Env, instantiate(Type)),
|
||||
case Type1 of
|
||||
{bytes_t, _, _} -> ok;
|
||||
_ -> type_error({cannot_unify, Type1, {bytes_t, [], any}, {at, Type}})
|
||||
_ ->
|
||||
type_error({unknown_byte_length, Type})
|
||||
end;
|
||||
check_bytes_constraint(Env, {add_bytes, Ann, Fun, A0, B0, C0}) ->
|
||||
A = unfold_types_in_type(Env, instantiate(A0)),
|
||||
B = unfold_types_in_type(Env, instantiate(B0)),
|
||||
C = unfold_types_in_type(Env, instantiate(C0)),
|
||||
case {A, B, C} of
|
||||
{{bytes_t, _, _M}, {bytes_t, _, _N}, {bytes_t, _, _R}} ->
|
||||
ok; %% If all are solved we checked M + N == R in solve_bytes_constraint.
|
||||
_ -> type_error({unsolved_bytes_constraint, Ann, Fun, A, B, C})
|
||||
end.
|
||||
|
||||
%% -- Field constraints --
|
||||
@ -1803,8 +1842,8 @@ unfold_types(Env, {typed, Attr, E, Type}, Options) ->
|
||||
{typed, Attr, unfold_types(Env, E, Options), unfold_types_in_type(Env, Type, Options)};
|
||||
unfold_types(Env, {arg, Attr, Id, Type}, Options) ->
|
||||
{arg, Attr, Id, unfold_types_in_type(Env, Type, Options)};
|
||||
unfold_types(Env, {type_sig, Ann, NamedArgs, Args, Ret}, Options) ->
|
||||
{type_sig, Ann,
|
||||
unfold_types(Env, {type_sig, Ann, Constr, NamedArgs, Args, Ret}, Options) ->
|
||||
{type_sig, Ann, Constr,
|
||||
unfold_types_in_type(Env, NamedArgs, Options),
|
||||
unfold_types_in_type(Env, Args, Options),
|
||||
unfold_types_in_type(Env, Ret, Options)};
|
||||
@ -1996,6 +2035,8 @@ occurs_check1(R, [H | T]) ->
|
||||
occurs_check(R, H) orelse occurs_check(R, T);
|
||||
occurs_check1(_, []) -> false.
|
||||
|
||||
fresh_uvar([{origin, system}]) ->
|
||||
error(oh_no_you_dont);
|
||||
fresh_uvar(Attrs) ->
|
||||
{uvar, Attrs, make_ref()}.
|
||||
|
||||
@ -2032,6 +2073,17 @@ freshen(Ann, [A | B]) ->
|
||||
freshen(_, X) ->
|
||||
X.
|
||||
|
||||
freshen_type_sig(Ann, TypeSig = {type_sig, _, Constr, _, _, _}) ->
|
||||
FunT = freshen_type(Ann, typesig_to_fun_t(TypeSig)),
|
||||
apply_typesig_constraint(Ann, Constr, FunT),
|
||||
FunT.
|
||||
|
||||
apply_typesig_constraint(_Ann, none, _FunT) -> ok;
|
||||
apply_typesig_constraint(Ann, bytes_concat, {fun_t, _, [], [A, B], C}) ->
|
||||
add_bytes_constraint({add_bytes, Ann, concat, A, B, C});
|
||||
apply_typesig_constraint(Ann, bytes_split, {fun_t, _, [], [C], {tuple_t, _, [A, B]}}) ->
|
||||
add_bytes_constraint({add_bytes, Ann, split, A, B, C}).
|
||||
|
||||
%% Dereferences all uvars and replaces the uninstantiated ones with a
|
||||
%% succession of tvars.
|
||||
instantiate(E) ->
|
||||
@ -2316,6 +2368,21 @@ mk_error({bad_top_level_decl, Decl}) ->
|
||||
Msg = io_lib:format("The definition of '~s' must appear inside a ~s.\n",
|
||||
[pp_expr("", Id), What]),
|
||||
mk_t_err(pos(Decl), Msg);
|
||||
mk_error({unknown_byte_length, Type}) ->
|
||||
Msg = io_lib:format("Cannot resolve length of byte array.\n", []),
|
||||
mk_t_err(pos(Type), Msg);
|
||||
mk_error({unsolved_bytes_constraint, Ann, concat, A, B, C}) ->
|
||||
Msg = io_lib:format("Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n"
|
||||
"~s (at ~s)\n~s (at ~s)\nand result type\n~s (at ~s)\n",
|
||||
[pp_type(" - ", A), pp_loc(A), pp_type(" - ", B),
|
||||
pp_loc(B), pp_type(" - ", C), pp_loc(C)]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({unsolved_bytes_constraint, Ann, split, A, B, C}) ->
|
||||
Msg = io_lib:format("Failed to resolve byte array lengths in call to Bytes.split with argument of type\n"
|
||||
"~s (at ~s)\nand result types\n~s (at ~s)\n~s (at ~s)\n",
|
||||
[ pp_type(" - ", C), pp_loc(C), pp_type(" - ", A), pp_loc(A),
|
||||
pp_type(" - ", B), pp_loc(B)]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error(Err) ->
|
||||
Msg = io_lib:format("Unknown error: ~p\n", [Err]),
|
||||
mk_t_err(pos(0, 0), Msg).
|
||||
@ -2443,7 +2510,7 @@ if_branches(If = {'if', Ann, _, Then, Else}) ->
|
||||
end;
|
||||
if_branches(E) -> [E].
|
||||
|
||||
pp_typed(Label, E, T = {type_sig, _, _, _, _}) -> pp_typed(Label, E, typesig_to_fun_t(T));
|
||||
pp_typed(Label, E, T = {type_sig, _, _, _, _, _}) -> pp_typed(Label, E, typesig_to_fun_t(T));
|
||||
pp_typed(Label, {typed, _, Expr, _}, Type) ->
|
||||
pp_typed(Label, Expr, Type);
|
||||
pp_typed(Label, Expr, Type) ->
|
||||
@ -2475,7 +2542,7 @@ pp_loc(T) ->
|
||||
plural(No, _Yes, [_]) -> No;
|
||||
plural(_No, Yes, _) -> Yes.
|
||||
|
||||
pp(T = {type_sig, _, _, _, _}) ->
|
||||
pp(T = {type_sig, _, _, _, _, _}) ->
|
||||
pp(typesig_to_fun_t(T));
|
||||
pp([]) ->
|
||||
"";
|
||||
|
@ -198,7 +198,7 @@ builtins() ->
|
||||
{["String"], [{"length", 1}, {"concat", 2}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]},
|
||||
{["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2},
|
||||
{"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]},
|
||||
{["Bytes"], [{"to_int", 1}, {"to_str", 1}]},
|
||||
{["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}]},
|
||||
{["Int"], [{"to_str", 1}]},
|
||||
{["Address"], [{"to_str", 1}, {"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]}
|
||||
],
|
||||
@ -427,6 +427,9 @@ expr_to_fcode(Env, Type, {qid, Ann, X}) ->
|
||||
validate_aens_resolve_type(Ann, ResType, AensType),
|
||||
TypeArgs = [{lit, {typerep, AensType}}],
|
||||
{builtin_u, B, Ar, TypeArgs};
|
||||
{builtin_u, B = bytes_split, Ar} ->
|
||||
{fun_t, _, _, _, {tuple_t, _, [{bytes_t, _, N}, _]}} = Type,
|
||||
{builtin_u, B, Ar, [{lit, {int, N}}]};
|
||||
Other -> Other
|
||||
end;
|
||||
|
||||
|
@ -496,6 +496,8 @@ is_builtin_fun({qid, _, ["Address", "is_contract"]}, _Icode) ->
|
||||
is_builtin_fun({qid, _, ["Address", "is_payable"]}, _Icode) -> true;
|
||||
is_builtin_fun({qid, _, ["Bytes", "to_int"]}, _Icode) -> true;
|
||||
is_builtin_fun({qid, _, ["Bytes", "to_str"]}, _Icode) -> true;
|
||||
is_builtin_fun({qid, _, ["Bytes", "concat"]}, _Icode) -> true;
|
||||
is_builtin_fun({qid, _, ["Bytes", "split"]}, _Icode) -> true;
|
||||
is_builtin_fun(_, _) -> false.
|
||||
|
||||
%% -- Code generation for builtin functions --
|
||||
@ -718,6 +720,13 @@ builtin_code(_, {qid, _, ["Bytes", "to_int"]}, [Bytes], _, _, Icode) ->
|
||||
builtin_code(_, {qid, _, ["Bytes", "to_str"]}, [Bytes], _, _, Icode) ->
|
||||
{typed, _, _, {bytes_t, _, N}} = Bytes,
|
||||
builtin_call({bytes_to_str, N}, [ast_body(Bytes, Icode)]);
|
||||
builtin_code(_, {qid, _, ["Bytes", "concat"]}, [A, B], [TypeA, TypeB], _, Icode) ->
|
||||
{bytes_t, _, M} = TypeA,
|
||||
{bytes_t, _, N} = TypeB,
|
||||
builtin_call({bytes_concat, M, N}, [ast_body(A, Icode), ast_body(B, Icode)]);
|
||||
builtin_code(_, {qid, _, ["Bytes", "split"]}, [A], _, ResType, Icode) ->
|
||||
{tuple_t, _, [{bytes_t, _, M}, {bytes_t, _, N}]} = ResType,
|
||||
builtin_call({bytes_split, M, N}, [ast_body(A, Icode)]);
|
||||
builtin_code(_As, Fun, _Args, _ArgsT, _RetT, _Icode) ->
|
||||
gen_error({missing_code_for, Fun}).
|
||||
|
||||
|
@ -90,7 +90,13 @@ option_some(X) -> {tuple, [{integer, 1}, X]}.
|
||||
-define(BSL(X, B), op('bsl', ?MUL(B, 8), X)).
|
||||
-define(BSR(X, B), op('bsr', ?MUL(B, 8), X)).
|
||||
|
||||
op(Op, A, B) -> {binop, Op, operand(A), operand(B)}.
|
||||
op(Op, A, B) -> simpl({binop, Op, operand(A), operand(B)}).
|
||||
|
||||
%% We generate a lot of B * 8 for integer B from BSL and BSR.
|
||||
simpl({binop, '*', {integer, A}, {integer, B}}) when A >= 0, B >= 0, A * B < 1 bsl 256 ->
|
||||
{integer, A * B};
|
||||
simpl(Op) -> Op.
|
||||
|
||||
|
||||
operand(A) when is_atom(A) -> v(A);
|
||||
operand(I) when is_integer(I) -> {integer, I};
|
||||
@ -162,6 +168,8 @@ builtin_function(BF) ->
|
||||
{baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X));
|
||||
{bytes_to_int, N} -> bfun(BF, builtin_bytes_to_int(N));
|
||||
{bytes_to_str, N} -> bfun(BF, builtin_bytes_to_str(N));
|
||||
{bytes_concat, A, B} -> bfun(BF, builtin_bytes_concat(A, B));
|
||||
{bytes_split, A, B} -> bfun(BF, builtin_bytes_split(A, B));
|
||||
bytes_to_str_worker -> bfun(BF, builtin_bytes_to_str_worker());
|
||||
string_reverse -> bfun(BF, builtin_string_reverse());
|
||||
string_reverse_ -> bfun(BF, builtin_string_reverse_())
|
||||
@ -576,6 +584,75 @@ builtin_string_reverse_() ->
|
||||
builtin_addr_to_str() ->
|
||||
{[{"a", word}], ?call({baseX_int, 58}, [?V(a)]), word}.
|
||||
|
||||
%% At most one word
|
||||
%% | ..... | ========= | ........ |
|
||||
%% Offs ^ ^- Len -^ TotalLen ^
|
||||
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen =< 32 ->
|
||||
%% Bytes are packed into a single word
|
||||
Masked =
|
||||
case Offs of
|
||||
0 -> Bytes;
|
||||
_ -> ?MOD(Bytes, 1 bsl ((32 - Offs) * 8))
|
||||
end,
|
||||
Unpadded =
|
||||
case 32 - (Offs + Len) of
|
||||
0 -> Masked;
|
||||
N -> ?BSR(Masked, N)
|
||||
end,
|
||||
case Len of
|
||||
32 -> Unpadded;
|
||||
_ -> ?BSL(Unpadded, 32 - Len)
|
||||
end;
|
||||
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen > 32 ->
|
||||
%% Bytes is a pointer to memory. The VM can read at non-aligned addresses.
|
||||
%% Might read one word more than necessary.
|
||||
Word = op('!', Offs, Bytes),
|
||||
case Len == 32 of
|
||||
true -> Word;
|
||||
_ -> ?BSL(?BSR(Word, 32 - Len), 32 - Len)
|
||||
end.
|
||||
|
||||
builtin_bytes_concat(A, B) ->
|
||||
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
|
||||
MkBytes = fun([W]) -> W;
|
||||
(Ws) -> {tuple, Ws} end,
|
||||
Words = fun(N) -> (N + 31) div 32 end,
|
||||
WordsRes = Words(A + B),
|
||||
Word = fun(I) when 32 * (I + 1) =< A -> bytes_slice(I * 32, 32, A, ?V(a));
|
||||
(I) when 32 * I < A ->
|
||||
Len = A rem 32,
|
||||
Hi = bytes_slice(32 * I, Len, A, ?V(a)),
|
||||
Lo = bytes_slice(0, min(32 - Len, B), B, ?V(b)),
|
||||
?ADD(Hi, ?BSR(Lo, Len));
|
||||
(I) ->
|
||||
Offs = 32 * I - A,
|
||||
Len = min(32, B - Offs),
|
||||
bytes_slice(Offs, Len, B, ?V(b))
|
||||
end,
|
||||
Body =
|
||||
case {A, B} of
|
||||
{0, _} -> ?V(b);
|
||||
{_, 0} -> ?V(a);
|
||||
_ -> MkBytes([ Word(I) || I <- lists:seq(0, WordsRes - 1) ])
|
||||
end,
|
||||
{[{"a", Type(A)}, {"b", Type(B)}], Body, Type(A + B)}.
|
||||
|
||||
builtin_bytes_split(A, B) ->
|
||||
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
|
||||
MkBytes = fun([W]) -> W;
|
||||
(Ws) -> {tuple, Ws} end,
|
||||
Word = fun(I, Max) ->
|
||||
bytes_slice(I, min(32, Max - I), A + B, ?V(c))
|
||||
end,
|
||||
Body =
|
||||
case {A, B} of
|
||||
{0, _} -> [?I(0), ?V(c)];
|
||||
{_, 0} -> [?V(c), ?I(0)];
|
||||
_ -> [MkBytes([ Word(I, A) || I <- lists:seq(0, A - 1, 32) ]),
|
||||
MkBytes([ Word(I, A + B) || I <- lists:seq(A, A + B - 1, 32) ])]
|
||||
end,
|
||||
{[{"c", Type(A + B)}], {tuple, Body}, {tuple, [Type(A), Type(B)]}}.
|
||||
|
||||
bytes_to_raw_string(N, Term) when N =< 32 ->
|
||||
{tuple, [?I(N), Term]};
|
||||
bytes_to_raw_string(N, Term) when N > 32 ->
|
||||
@ -583,3 +660,4 @@ bytes_to_raw_string(N, Term) when N > 32 ->
|
||||
end,
|
||||
Words = (N + 31) div 32,
|
||||
?LET(bin, Term, {tuple, [?I(N) | [Elem(I) || I <- lists:seq(0, Words - 1)]]}).
|
||||
|
||||
|
@ -59,7 +59,8 @@ pos(File, Line, Col) ->
|
||||
-spec throw(_) -> ok | no_return().
|
||||
throw([]) -> ok;
|
||||
throw(Errs) when is_list(Errs) ->
|
||||
erlang:throw({error, Errs});
|
||||
SortedErrs = lists:sort(fun(E1, E2) -> E1#err.pos =< E2#err.pos end, Errs),
|
||||
erlang:throw({error, SortedErrs});
|
||||
throw(#err{} = Err) ->
|
||||
erlang:throw({error, [Err]}).
|
||||
|
||||
|
@ -106,6 +106,8 @@
|
||||
Op =:= 'AUTH_TX_HASH' orelse
|
||||
Op =:= 'BYTES_TO_INT' orelse
|
||||
Op =:= 'BYTES_TO_STR' orelse
|
||||
Op =:= 'BYTES_CONCAT' orelse
|
||||
Op =:= 'BYTES_SPLIT' orelse
|
||||
Op =:= 'ORACLE_CHECK' orelse
|
||||
Op =:= 'ORACLE_CHECK_QUERY' orelse
|
||||
Op =:= 'IS_ORACLE' orelse
|
||||
@ -490,6 +492,10 @@ builtin_to_scode(Env, bytes_to_int, [_] = Args) ->
|
||||
call_to_scode(Env, aeb_fate_ops:bytes_to_int(?a, ?a), Args);
|
||||
builtin_to_scode(Env, bytes_to_str, [_] = Args) ->
|
||||
call_to_scode(Env, aeb_fate_ops:bytes_to_str(?a, ?a), Args);
|
||||
builtin_to_scode(Env, bytes_concat, [_, _] = Args) ->
|
||||
call_to_scode(Env, aeb_fate_ops:bytes_concat(?a, ?a, ?a), Args);
|
||||
builtin_to_scode(Env, bytes_split, [_, _] = Args) ->
|
||||
call_to_scode(Env, aeb_fate_ops:bytes_split(?a, ?a, ?a), Args);
|
||||
builtin_to_scode(Env, abort, [_] = Args) ->
|
||||
call_to_scode(Env, aeb_fate_ops:abort(?a), Args);
|
||||
builtin_to_scode(Env, chain_spend, [_, _] = Args) ->
|
||||
@ -846,6 +852,8 @@ attributes(I) ->
|
||||
{'AUTH_TX_HASH', A} -> Pure(A, []);
|
||||
{'BYTES_TO_INT', A, B} -> Pure(A, [B]);
|
||||
{'BYTES_TO_STR', A, B} -> Pure(A, [B]);
|
||||
{'BYTES_CONCAT', A, B, C} -> Pure(A, [B, C]);
|
||||
{'BYTES_SPLIT', A, B, C} -> Pure(A, [B, C]);
|
||||
{'ORACLE_CHECK', A, B, C, D} -> Impure(A, [B, C, D]);
|
||||
{'ORACLE_CHECK_QUERY', A, B, C, D, E} -> Impure(A, [B, C, D, E]);
|
||||
{'IS_ORACLE', A, B} -> Impure(A, [B]);
|
||||
|
@ -143,6 +143,7 @@ compilable_contracts() ->
|
||||
"address_chain",
|
||||
"namespace_bug",
|
||||
"bytes_to_x",
|
||||
"bytes_concat",
|
||||
"aens",
|
||||
"tuple_match",
|
||||
"cyclic_include",
|
||||
@ -511,6 +512,50 @@ failing_contracts() ->
|
||||
[<<?Pos(3, 5)
|
||||
"Unbound variable Chain.event at line 3, column 5\n"
|
||||
"Did you forget to define the event type?">>])
|
||||
, ?TYPE_ERROR(bad_bytes_concat,
|
||||
[<<?Pos(12, 40)
|
||||
"Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n"
|
||||
" - 'g (at line 12, column 20)\n"
|
||||
" - 'h (at line 12, column 23)\n"
|
||||
"and result type\n"
|
||||
" - bytes(10) (at line 12, column 28)">>,
|
||||
<<?Pos(13, 28)
|
||||
"Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n"
|
||||
" - 'd (at line 13, column 20)\n"
|
||||
" - 'e (at line 13, column 23)\n"
|
||||
"and result type\n"
|
||||
" - 'f (at line 13, column 14)">>,
|
||||
<<?Pos(15, 5)
|
||||
"Cannot unify bytes(26)\n"
|
||||
" and bytes(25)\n"
|
||||
"at line 15, column 5">>,
|
||||
<<?Pos(17, 5)
|
||||
"Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n"
|
||||
" - bytes(6) (at line 16, column 24)\n"
|
||||
" - 'b (at line 16, column 34)\n"
|
||||
"and result type\n"
|
||||
" - 'c (at line 16, column 39)">>,
|
||||
<<?Pos(19, 25)
|
||||
"Cannot resolve length of byte array.">>])
|
||||
, ?TYPE_ERROR(bad_bytes_split,
|
||||
[<<?Pos(13, 5)
|
||||
"Failed to resolve byte array lengths in call to Bytes.split with argument of type\n"
|
||||
" - 'f (at line 12, column 20)\n"
|
||||
"and result types\n"
|
||||
" - 'e (at line 13, column 5)\n"
|
||||
" - bytes(20) (at line 12, column 29)">>,
|
||||
<<?Pos(16, 5)
|
||||
"Failed to resolve byte array lengths in call to Bytes.split with argument of type\n"
|
||||
" - bytes(15) (at line 15, column 24)\n"
|
||||
"and result types\n"
|
||||
" - 'c (at line 16, column 5)\n"
|
||||
" - 'd (at line 16, column 5)">>,
|
||||
<<?Pos(19, 5)
|
||||
"Failed to resolve byte array lengths in call to Bytes.split with argument of type\n"
|
||||
" - 'b (at line 18, column 20)\n"
|
||||
"and result types\n"
|
||||
" - bytes(20) (at line 18, column 25)\n"
|
||||
" - 'a (at line 19, column 5)">>])
|
||||
].
|
||||
|
||||
-define(Path(File), "code_errors/" ??File).
|
||||
|
19
test/contracts/bad_bytes_concat.aes
Normal file
19
test/contracts/bad_bytes_concat.aes
Normal file
@ -0,0 +1,19 @@
|
||||
contract BytesConcat =
|
||||
|
||||
entrypoint test1(x : bytes(10), y : bytes(20)) =
|
||||
Bytes.concat(x, y)
|
||||
|
||||
entrypoint test2(x : bytes(10), y) : bytes(15) =
|
||||
Bytes.concat(x, y)
|
||||
|
||||
entrypoint test3(x, y : bytes(20)) : bytes(25) =
|
||||
Bytes.concat(x, y)
|
||||
|
||||
entrypoint fail1(x, y) : bytes(10) = Bytes.concat(x, y)
|
||||
entrypoint fail2(x, y) = Bytes.concat(x, y)
|
||||
entrypoint fail3(x : bytes(6), y : bytes(20)) : bytes(25) =
|
||||
Bytes.concat(x, y)
|
||||
entrypoint fail4(x : bytes(6), y) : _ =
|
||||
Bytes.concat(x, y)
|
||||
|
||||
entrypoint fail5(x) = Bytes.to_str(x)
|
20
test/contracts/bad_bytes_split.aes
Normal file
20
test/contracts/bad_bytes_split.aes
Normal file
@ -0,0 +1,20 @@
|
||||
contract BytesSplit =
|
||||
|
||||
entrypoint test1(x) : bytes(10) * bytes(20) =
|
||||
Bytes.split(x)
|
||||
|
||||
entrypoint test2(x : bytes(15)) : bytes(10) * _ =
|
||||
Bytes.split(x)
|
||||
|
||||
entrypoint test3(x : bytes(25)) : _ * bytes(20) =
|
||||
Bytes.split(x)
|
||||
|
||||
entrypoint fail1(x) : _ * bytes(20) =
|
||||
Bytes.split(x)
|
||||
|
||||
entrypoint fail2(x : bytes(15)) : _ =
|
||||
Bytes.split(x)
|
||||
|
||||
entrypoint fail3(x) : bytes(20) * _ =
|
||||
Bytes.split(x)
|
||||
|
4
test/contracts/bytes_concat.aes
Normal file
4
test/contracts/bytes_concat.aes
Normal file
@ -0,0 +1,4 @@
|
||||
contract BytesConcat =
|
||||
entrypoint rot(a : bytes(3)) =
|
||||
switch (Bytes.split(a))
|
||||
(b, c) => Bytes.concat(c : bytes(2), b)
|
Loading…
x
Reference in New Issue
Block a user