Merge pull request #147 from aeternity/bytes-concat

Bytes.concat and Bytes.split
This commit is contained in:
Ulf Norell 2019-09-09 19:14:00 +02:00 committed by GitHub
commit 92ac8b1f02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 299 additions and 45 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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));
{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([]) ->
"";

View File

@ -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;

View File

@ -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}).

View File

@ -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)]]}).

View File

@ -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]}).

View File

@ -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]);

View File

@ -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).

View 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)

View 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)

View File

@ -0,0 +1,4 @@
contract BytesConcat =
entrypoint rot(a : bytes(3)) =
switch (Bytes.split(a))
(b, c) => Bytes.concat(c : bytes(2), b)