Handle argument and data serialization/deserialization.

This commit is contained in:
Erik Stenman 2019-02-14 14:38:38 +01:00
parent 25319fccd4
commit 23ed6caf64
7 changed files with 706 additions and 19 deletions

55
include/aefa_data.hrl Normal file
View File

@ -0,0 +1,55 @@
-define(FATE_INTEGER_T, integer()).
-define(FATE_BYTE_T, 0..255).
-define(FATE_BOOLEAN_T, true | false).
-define(FATE_NIL_T, []).
-define(FATE_LIST_T, list()).
-define(FATE_UNIT_T, {tuple, {}}).
-define(FATE_MAP_T, #{ fate_type() => fate_type() }).
-define(FATE_STRING_T, binary()).
-define(FATE_ADDRESS_T, {address, <<_:256>>}).
-define(FATE_VARIANT_T, {variant, ?FATE_BYTE_T, ?FATE_BYTE_T, tuple()}).
-define(FATE_VOID_T, void).
-define(FATE_TUPLE_T, {tuple, tuple()}).
-define(FATE_BITS_T, {bits, integer()}).
-define(IS_FATE_INTEGER(X), is_integer(X)).
-define(IS_FATE_LIST(X), (is_list(X))).
-define(IS_FATE_STRING(X), (is_binary(X))).
-define(IS_FATE_MAP(X), (is_map(X))).
-define(IS_FATE_TUPLE(X), (is_tuple(X) andalso (tuple == element(1, X) andalso is_tuple(element(2, X))))).
-define(IS_FATE_ADDRESS(X), (is_tuple(X) andalso (address == element(1, X) andalso is_binary(element(2, X))))).
-define(IS_FATE_BITS(X), (is_tuple(X) andalso (bits == element(1, X) andalso is_integer(element(2, X))))).
-define(IS_FATE_VARIANT(X), (is_tuple(X)
andalso
(variant == element(1, X)
andalso is_integer(element(2, X))
andalso is_integer(element(3, X))
andalso is_tuple(element(4, X))
))).
-define(IS_FATE_BOOLEAN(X), is_boolean(X)).
-define(FATE_UNIT, {tuple, {}}).
-define(FATE_TUPLE(T), {tuple, T}).
-define(FATE_ADDRESS(A), {address, A}).
-define(FATE_BITS(B), {bits, B}).
-define(FATE_INTEGER_VALUE(X), (X)).
-define(FATE_LIST_VALUE(X), (X)).
-define(FATE_STRING_VALUE(X), (X)).
-define(FATE_ADDRESS_VALUE(X), (element(2, X))).
-define(FATE_MAP_VALUE(X), (X)).
-define(FATE_MAP_SIZE(X), (map_size(X))).
-define(FATE_STRING_SIZE(X), (byte_size(X))).
-define(FATE_TRUE, true).
-define(FATE_FALSE, false).
-define(FATE_NIL, []).
-define(FATE_VOID, void).
-define(FATE_EMPTY_STRING, <<>>).
-define(FATE_STRING(S), S).
-define(FATE_VARIANT(Size, Tag,T), {variant, Size, Tag, T}).
-define(MAKE_FATE_INTEGER(X), X).
-define(MAKE_FATE_LIST(X), X).
-define(MAKE_FATE_MAP(X), X).
-define(MAKE_FATE_STRING(X), X).

91
src/ae_rlp.erl Normal file
View File

@ -0,0 +1,91 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc
%%% Implementation of the Recursive Length Prefix.
%%%
%%% https://github.com/ethereum/wiki/wiki/RLP
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(ae_rlp).
-export([ decode/1
, decode_one/1
, encode/1
]).
-export_type([ encodable/0
, encoded/0
]).
-type encodable() :: [encodable()] | binary().
-type encoded() :: <<_:8, _:_*8>>.
-define(UNTAGGED_SIZE_LIMIT , 55).
-define(UNTAGGED_LIMIT , 127).
-define(BYTE_ARRAY_OFFSET , 128).
-define(LIST_OFFSET , 192).
-spec encode(encodable()) -> encoded().
encode(X) ->
encode(X, []).
encode(<<B>> = X,_Opts) when B =< ?UNTAGGED_LIMIT ->
%% An untagged value
X;
encode(X,_Opts) when is_binary(X) ->
%% Byte array
add_size(?BYTE_ARRAY_OFFSET, X);
encode(L, Opts) when is_list(L) ->
%% Lists items are encoded and concatenated
ByteArray = << << (encode(X, Opts))/binary >> || X <- L >>,
add_size(?LIST_OFFSET, ByteArray).
add_size(Offset, X) when byte_size(X) =< ?UNTAGGED_SIZE_LIMIT ->
%% The size fits in one tagged byte
<<(Offset + byte_size(X)), X/binary>>;
add_size(Offset, X) when is_binary(X) ->
%% The size itself needs to be encoded as a byte array
%% Add the tagged size of the size byte array
SizeBin = binary:encode_unsigned(byte_size(X)),
TaggedSize = ?UNTAGGED_SIZE_LIMIT + Offset + byte_size(SizeBin),
true = (TaggedSize < 256 ), %% Assert
<<TaggedSize, SizeBin/binary, X/binary>>.
-spec decode(encoded()) -> encodable().
decode(Bin) when is_binary(Bin), byte_size(Bin) > 0 ->
case decode_one(Bin) of
{X, <<>>} -> X;
{X, Left} -> error({trailing, X, Bin, Left})
end.
decode_one(<<X, B/binary>>) when X =< ?UNTAGGED_LIMIT ->
%% Untagged value
{<<X>>, B};
decode_one(<<L, _/binary>> = B) when L < ?LIST_OFFSET ->
%% Byte array
{Size, Rest} = decode_size(B, ?BYTE_ARRAY_OFFSET),
<<X:Size/binary, Tail/binary>> = Rest,
{X, Tail};
decode_one(<<_/binary>> = B) ->
%% List
{Size, Rest} = decode_size(B, ?LIST_OFFSET),
<<X:Size/binary, Tail/binary>> = Rest,
{decode_list(X), Tail}.
decode_size(<<L, B/binary>>, Offset) when L =< Offset + ?UNTAGGED_SIZE_LIMIT->
%% One byte tagged size.
{L - Offset, B};
decode_size(<<_, 0, _/binary>>,_Offset) ->
error(leading_zeroes_in_size);
decode_size(<<L, B/binary>>, Offset) ->
%% Actual size is in a byte array.
BinSize = L - Offset - ?UNTAGGED_SIZE_LIMIT,
<<Size:BinSize/unit:8, Rest/binary>> = B,
{Size, Rest}.
decode_list(<<>>) -> [];
decode_list(B) ->
{Element, Rest} = decode_one(B),
[Element|decode_list(Rest)].

View File

@ -172,23 +172,105 @@ deserialize_op(Op, Rest, Code) ->
OpName = aefa_opcodes:mnemonic(Op),
case aefa_opcodes:args(Op) of
0 -> {Rest, [OpName | Code]};
1 -> %% TODO: use rlp encoded int.
<<Arg:8, Rest2/binary>> = Rest,
{Rest2, [Arg, OpName | Code]};
hash ->
<<A, B, C, D, Rest2/binary>> = Rest,
Code2 = [<<A,B,C,D>>, OpName | Code],
{Rest2, Code2}
1 ->
<<ArgType:8, Rest2/binary>> = Rest,
{Arg, Rest3} = aefa_encoding:deserialize_one(Rest2),
Modifier = bits_to_modifier(ArgType),
{Rest3, [{OpName, {Modifier, Arg}} | Code]};
2 ->
<<ArgType:8, Rest2/binary>> = Rest,
{Arg0, Rest3} = aefa_encoding:deserialize_one(Rest2),
{Arg1, Rest4} = aefa_encoding:deserialize_one(Rest3),
Modifier = bits_to_modifier(ArgType band 2#11),
Modifier2 = bits_to_modifier((ArgType bsr 2) band 2#11),
{Rest4, [{OpName, {Modifier, Arg0}, {Modifier2, Arg1}} | Code]}
end.
serialize(#{functions := Functions} = Env) ->
%% TODO: add serialization of immediates
%% TODO: add serialization of function definitions
Code = [[?FUNCTION, Name, serialize_signature(Sig), C] ||
{Name, {Sig, C}} <- maps:to_list(Functions)],
lists:flatten(Code).
serialize_code(lists:flatten(Code)).
%% Argument encoding
%% Agument Specification Byte
%% bitpos: 6 4 2 0
%% xx xx xx xx
%% Arg3 Arg2 Arg1 Arg0
%% Bit pattern
%% 00 : stack/unused (depending on instruction)
%% 01 : argN
%% 10 : varN
%% 11 : immediate
%% TODO: serialize complex immediates
serialize_code([ {Arg0Type, Arg0}
, {Arg1Type, Arg1}
, {Arg2Type, Arg2}
, {Arg3Type, Arg3}| Rest]) ->
ArgSpec =
modifier_bits(Arg0Type) bor
(modifier_bits(Arg1Type) bsl 2) bor
(modifier_bits(Arg2Type) bsl 4) bor
(modifier_bits(Arg3Type) bsl 6),
[ ArgSpec
, serialize_data(Arg0Type, Arg0)
, serialize_data(Arg1Type, Arg1)
, serialize_data(Arg2Type, Arg2)
, serialize_data(Arg3Type, Arg3)
| serialize_code(Rest)];
serialize_code([ {Arg0Type, Arg0}
, {Arg1Type, Arg1}
, {Arg2Type, Arg2}
| Rest]) ->
ArgSpec =
modifier_bits(Arg0Type) bor
(modifier_bits(Arg1Type) bsl 2) bor
(modifier_bits(Arg2Type) bsl 4),
[ArgSpec
, serialize_data(Arg0Type, Arg0)
, serialize_data(Arg1Type, Arg1)
, serialize_data(Arg2Type, Arg2)
| serialize_code(Rest)];
serialize_code([ {Arg0Type, Arg0}
, {Arg1Type, Arg1}
| Rest]) ->
ArgSpec =
modifier_bits(Arg0Type) bor
(modifier_bits(Arg1Type) bsl 2),
[ArgSpec
, serialize_data(Arg0Type, Arg0)
, serialize_data(Arg1Type, Arg1)
| serialize_code(Rest)];
serialize_code([ {Arg0Type, Arg0} | Rest]) ->
ArgSpec =
modifier_bits(Arg0Type),
[ArgSpec
, serialize_data(Arg0Type, Arg0)
| serialize_code(Rest)];
serialize_code([B|Rest]) ->
[B | serialize_code(Rest)];
serialize_code([]) -> [].
%% 00 : stack/unused (depending on instruction)
%% 01 : argN
%% 10 : varN
%% 11 : immediate
modifier_bits(immediate) -> 2#11;
modifier_bits(var) -> 2#10;
modifier_bits(arg) -> 2#01;
modifier_bits(stack) -> 2#00.
bits_to_modifier(2#11) -> immediate;
bits_to_modifier(2#10) -> var;
bits_to_modifier(2#01) -> arg;
bits_to_modifier(2#00) -> stack.
serialize_data(_, Data) ->
aefa_encoding:serialize(Data).
serialize_signature({Args, RetType}) ->
[serialize_type({tuple, Args}) |
@ -247,15 +329,19 @@ to_bytecode([{mnemonic,_line, Op}|Rest], Address, Env, Code, Opts) ->
OpCode = aefa_opcodes:m_to_op(Op),
%% TODO: arguments
to_bytecode(Rest, Address, Env, [OpCode|Code], Opts);
to_bytecode([{arg,_line, N}|Rest], Address, Env, Code, Opts) ->
to_bytecode(Rest, Address, Env, [{arg, N}|Code], Opts);
to_bytecode([{var,_line, N}|Rest], Address, Env, Code, Opts) ->
to_bytecode(Rest, Address, Env, [{var, N}|Code], Opts);
to_bytecode([{stack,_line, N}|Rest], Address, Env, Code, Opts) ->
to_bytecode(Rest, Address, Env, [{stack, N}|Code], Opts);
to_bytecode([{int,_line, Int}|Rest], Address, Env, Code, Opts) ->
to_bytecode(Rest, Address, Env, [Int|Code], Opts);
to_bytecode(Rest, Address, Env, [{immediate, Int}|Code], Opts);
to_bytecode([{hash,_line, Hash}|Rest], Address, Env, Code, Opts) ->
to_bytecode(Rest, Address, Env, [Hash|Code], Opts);
to_bytecode(Rest, Address, Env, [{immediate, Hash}|Code], Opts);
to_bytecode([{id,_line, ID}|Rest], Address, Env, Code, Opts) ->
{ok, Hash} = lookup_symbol(ID, Env),
to_bytecode(Rest, Address, Env, [Hash|Code], Opts);
to_bytecode([{label,_line, Label}|Rest], Address, Env, Code, Opts) ->
to_bytecode(Rest, Address, Env#{Label => Address}, Code, Opts);
{Hash, Env2} = insert_symbol(ID, Env),
to_bytecode(Rest, Address, Env2, [{immediate, Hash}|Code], Opts);
to_bytecode([], Address, Env, Code, Opts) ->
Env2 = insert_fun(Address, Code, Env),
case proplists:lookup(pp_opcodes, Opts) of

View File

@ -20,7 +20,10 @@ ID = {LOWER}[a-zA-Z0-9_]*
Rules.
%%{ID} : {token, {id, TokenLine, TokenChars }}.
arg{INT} : {token, {arg, TokenLine, parse_arg(TokenChars)}}.
var{INT} : {token, {var, TokenLine, parse_var(TokenChars)}}.
a : {token, {stack, TokenLine, 0}}.
a{INT} : {token, {stack, TokenLine, parse_acc(TokenChars)}}.
RETURN : {token, {mnemonic, TokenLine, 'RETURN'}}.
CALL : {token, {mnemonic, TokenLine, 'CALL'}}.
@ -135,7 +138,6 @@ COMMENT : {token, {mnemonic, TokenLine, 'COMMENT'}}.
\-\> : {token, {'to', TokenLine}}.
\: : {token, {'to', TokenLine}}.
, : {token, {',', TokenLine}}.
\. : {token, {'.', TokenLine}}.
\( : {token, {'(', TokenLine}}.
\) : {token, {')', TokenLine}}.
\[ : {token, {'[', TokenLine}}.
@ -143,6 +145,8 @@ COMMENT : {token, {mnemonic, TokenLine, 'COMMENT'}}.
\{ : {token, {'{', TokenLine}}.
\} : {token, {'}', TokenLine}}.
\. : skip_token.
%% Whitespace ignore
{WS} : skip_token.
@ -167,6 +171,11 @@ parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16).
parse_int(Chars) -> list_to_integer(Chars).
parse_arg("arg" ++ N) -> list_to_integer(N).
parse_var("var" ++ N) -> list_to_integer(N).
parse_acc("a" ++ N) -> list_to_integer(N).
parse_hash("#" ++ Chars) ->
N = list_to_integer(Chars, 16),
<<N:256>>.

180
src/aefa_data.erl Normal file
View File

@ -0,0 +1,180 @@
%% First draft of FATE data representation.
%% Very likely to change.
%%
-include("aefa_data.hrl").
-module(aefa_data).
-type fate_integer() :: ?FATE_INTEGER_T.
-type fate_boolean() :: ?FATE_BOOLEAN_T.
-type fate_nil() :: ?FATE_NIL_T.
-type fate_list() :: ?FATE_LIST_T.
-type fate_unit() :: ?FATE_UNIT_T.
-type fate_map() :: ?FATE_MAP_T.
-type fate_string() :: ?FATE_STRING_T.
-type fate_address() :: ?FATE_ADDRESS_T.
-type fate_variant() :: ?FATE_VARIANT_T.
-type fate_void() :: ?FATE_VOID_T.
-type fate_tuple() :: ?FATE_TUPLE_T.
-type fate_type() ::
fate_boolean()
| fate_integer()
| fate_nil()
| fate_list()
| fate_unit()
| fate_tuple()
| fate_string()
| fate_address()
| fate_variant()
| fate_map()
| fate_list()
| fate_tuple()
| fate_void(). %% Not sure we need this.
-export_type([fate_type/0]).
-export([ make_integer/1
, make_boolean/1
, make_list/1
, make_variant/3
, make_tuple/1
, make_string/1
, make_map/1
, make_address/1
, make_bits/1
, make_unit/0
, tuple_to_list/1
, decode/1
, encode/1
]).
-export([format/1]).
make_integer(I) when is_integer(I) -> ?MAKE_FATE_INTEGER(I).
make_boolean(true) -> ?FATE_TRUE;
make_boolean(false) -> ?FATE_FALSE.
make_list([]) -> ?FATE_NIL;
make_list(L) -> ?MAKE_FATE_LIST(L).
make_string(S) when is_list(S) ->
?FATE_STRING(list_to_binary(lists:flatten(S)));
make_string(S) when is_binary(S) -> ?FATE_STRING(S).
make_unit() -> ?FATE_UNIT.
make_tuple(T) -> ?FATE_TUPLE(T).
make_map(M) -> ?MAKE_FATE_MAP(M).
make_address(A) -> ?FATE_ADDRESS(A).
make_bits(I) when is_integer(I) -> ?FATE_BITS(I).
make_variant(Size, Tag, Values) when is_integer(Size), is_integer(Tag)
, 0 =< Size
, 0 =< Tag
, Tag < Size
, is_tuple(Values) ->
?FATE_VARIANT(Size, Tag, Values).
tuple_to_list(?FATE_TUPLE(T)) -> erlang:tuple_to_list(T).
%% Encode is a convinience function for testing, encoding an Erlang term
%% to a Fate term, but it can not distinguish between e.g. 32-byte strings
%% and addresses. Therfore an extra tuple layer on the erlang side for
%% addresses and bits.
encode({bits, Term}) when is_integer(Term) -> make_bits(Term);
%% TODO: check that each byte is in base58
encode({address, B}) when is_binary(B) -> make_address(B);
encode({address, I}) when is_integer(I) -> B = <<I:256>>, make_address(B);
encode({address, S}) when is_list(S) -> make_address(base58_to_address(S));
encode({variant, Size, Tag, Values}) -> make_variant(Size, Tag, Values);
encode(Term) when is_integer(Term) -> make_integer(Term);
encode(Term) when is_boolean(Term) -> make_boolean(Term);
encode(Term) when is_list(Term) -> make_list([encode(E) || E <- Term]);
encode(Term) when is_tuple(Term) ->
make_tuple(list_to_tuple([encode(E) || E <- erlang:tuple_to_list(Term)]));
encode(Term) when is_map(Term) ->
make_map(maps:from_list([{encode(K), encode(V)} || {K,V} <- maps:to_list(Term)]));
encode(Term) when is_binary(Term) -> make_string(Term).
decode(I) when ?IS_FATE_INTEGER(I) -> I;
decode(?FATE_TRUE) -> true;
decode(?FATE_FALSE) -> false;
decode(L) when ?IS_FATE_LIST(L) -> [decode(E) || E <- L];
decode(?FATE_ADDRESS(<<Address:256>>)) -> {address, Address};
decode(?FATE_BITS(Bits)) -> {bits, Bits};
decode(?FATE_TUPLE(T)) -> erlang:list_to_tuple([decode(E) || E <- T]);
decode(?FATE_VARIANT(Size, Tag, Values)) -> {variant, Size, Tag, Values};
decode(S) when ?IS_FATE_STRING(S) -> binary_to_list(S);
decode(M) when ?IS_FATE_MAP(M) ->
maps:from_list([{decode(K), decode(V)} || {K, V} <- maps:to_list(M)]).
-spec format(fate_type()) -> iolist().
format(I) when ?IS_FATE_INTEGER(I) -> integer_to_list(?MAKE_FATE_INTEGER(I));
format(?FATE_VOID) -> "void";
format(?FATE_TRUE) -> "true";
format(?FATE_FALSE) -> "false";
format(?FATE_NIL) -> "[]";
format(L) when ?IS_FATE_LIST(L) -> format_list(?FATE_LIST_VALUE(L));
format(?FATE_UNIT) -> "unit";
format(?FATE_TUPLE(T)) ->
"{ " ++ [format(E) ++ " " || E <- erlang:tuple_to_list(T)] ++ "}";
format(S) when ?IS_FATE_STRING(S) -> [S];
format(?FATE_VARIANT(Size, Tag, T)) ->
"( " ++ integer_to_list(Size) ++ ", "
++ integer_to_list(Tag) ++ ", "
++ [format(E) ++ " " || E <- erlang:tuple_to_list(T)]
++ " )";
format(M) when ?IS_FATE_MAP(M) ->
"#{ "
++ format_kvs(maps:to_list(?FATE_MAP_VALUE(M)))
++" }";
format(?FATE_ADDRESS(Address)) -> base58:binary_to_base58(Address);
format(V) -> exit({not_a_fate_type, V}).
format_list([]) -> " ]";
format_list([E]) -> format(E) ++ " ]";
format_list([H|T]) -> format(H) ++ ", " ++ format_list(T).
format_kvs([]) -> "";
format_kvs([{K,V}]) -> "( " ++ format(K) ++ " => " ++ format(V) ++ " )";
format_kvs([{K,V} | Rest]) ->
"( " ++ format(K) ++ " => " ++ format(V) ++ " ), " ++ format_kvs(Rest).
%% -- Local base 58 library
base58char(Char) ->
binary:at(<<"123456789ABCDEFGHJKLMNPQRSTUVWXYZ"
"abcdefghijkmnopqrstuvwxyz">>, Char).
char_to_base58(C) ->
binary:at(<<0,1,2,3,4,5,6,7,8,0,0,0,0,0,0,0,9,10,11,12,13,14,15,16,0,17,
18,19,20,21,0,22,23,24,25,26,27,28,29,30,31,32,0,0,0,0,0,0,
33,34,35,36,37,38,39,40,41,42,43,0,44,45,46,47,48,49,50,51,
52,53,54,55,56,57>>, C-$1).
base58_to_integer(C, []) -> C;
base58_to_integer(C, [X | Xs]) ->
base58_to_integer(C * 58 + char_to_base58(X), Xs).
base58_to_integer([]) -> error;
base58_to_integer([Char]) -> char_to_base58(Char);
base58_to_integer([Char | Str]) ->
base58_to_integer(char_to_base58(Char), Str).
base58_to_address(Base58) ->
I = base58_to_integer(Base58),
Bin = <<I:256>>,
Bin.
integer_to_base58(0) -> <<"1">>;
integer_to_base58(Integer) ->
Base58String = integer_to_base58(Integer, []),
list_to_binary(Base58String).
integer_to_base58(0, Acc) -> Acc;
integer_to_base58(Integer, Acc) ->
Quot = Integer div 58,
Rem = Integer rem 58,
integer_to_base58(Quot, [base58char(Rem)|Acc]).

262
src/aefa_encoding.erl Normal file
View File

@ -0,0 +1,262 @@
%% Fate data (and instruction) serialization.
%%
%% The FATE serialization has to fullfill the following properties:
%% * There has to be 1 and only 1 byte sequence
%% representing each unique value in FATE.
%% * A valid byte sequence has to be deserializable to a FATE value.
%% * A valid byte sequence must not contain any trailing bytes.
%% * A serialization is a sequence of 8-bit bytes.
%%
%% The serialization function should fullfill the following:
%% * A valid FATE value should be serialized to a byte sequence.
%% * Any other argument, not representing a valid FATE value should
%% throw an exception
%%
%% The deserialization function should fullfill the following:
%% * A valid byte sequence should be deserialized to a valid FATE value.
%% * Any other argument, not representing a valid byte sequence should
%% throw an exception
%%
%% History
%% * First draft of FATE serialization encoding/decoding.
%% Initial experiment with tags
%% * Second draft
%% * FATE data is now defined in aefa_data.erl
%% * Third draft
%% * Added Bit strings
%%
%% TODO:
%% * Make the code production ready.
%% (add tests, document exported functions).
%% * Handle Variant types better.
%% * Handle type representations.
%% * Handle instructions.
%%
%% ------------------------------------------------------------------------
-module(aefa_encoding).
-export([ deserialize/1
, deserialize_one/1
, serialize/1
]).
-include("aefa_data.hrl").
%% Definition of tag scheme.
%% This has to follow the protocol specification.
-define(SMALL_INT , 2#0). %% sxxxxxx 0 - 6 bit integer with sign bit
%% 1 Set below
-define(LONG_STRING , 2#00000001). %% 000000 01 - RLP encoded array, size >= 64
-define(SHORT_STRING , 2#01). %% xxxxxx 01 - [bytes], 0 < xxxxxx:size < 64
%% 11 Set below
-define(SHORT_LIST , 2#0011). %% xxxx 0011 - [encoded elements], 0 < length < 16
%% xxxx 0111 - FREE (For typedefs in future)
-define(LONG_TUPLE , 2#00001011). %% 0000 1011 - RLP encoded (size - 16) + [encoded elements],
-define(SHORT_TUPLE , 2#1011). %% xxxx 1011 - [encoded elements], 0 < size < 16
%% 1111 Set below
-define(LONG_LIST , 2#00011111). %% 0001 1111 - RLP encoded (length - 16) + [Elements]
-define(MAP , 2#00101111). %% 0010 1111 - RLP encoded size + [encoded key, encoded value]
-define(EMPTY_TUPLE , 2#00111111). %% 0011 1111
-define(POS_BITS , 2#01001111). %% 0100 1111 - RLP encoded integer (to be interpreted as bitfield)
-define(EMPTY_STRING , 2#01011111). %% 0101 1111
-define(POS_BIG_INT , 2#01101111). %% 0110 1111 - RLP encoded (integer - 64)
-define(FALSE , 2#01111111). %% 0111 1111
%% %% 1000 1111 - FREE (Possibly for bytecode in the future.)
-define(ADDRESS , 2#10011111). %% 1001 1111 - [32 bytes]
-define(VARIANT , 2#10101111). %% 1010 1111 - encoded size + encoded tag + encoded values
-define(NIL , 2#10111111). %% 1011 1111 - Empty list
-define(NEG_BITS , 2#11001111). %% 1100 1111 - RLP encoded integer (infinite 1:s bitfield)
-define(EMPTY_MAP , 2#11011111). %% 1101 1111
-define(NEG_BIG_INT , 2#11101111). %% 1110 1111 - RLP encoded (integer - 64)
-define(TRUE , 2#11111111). %% 1111 1111
-define(SHORT_TUPLE_SIZE, 16).
-define(SHORT_LIST_SIZE , 16).
-define(SMALL_INT_SIZE , 64).
-define(SHORT_STRING_SIZE, 64).
-define(POS_SIGN, 0).
-define(NEG_SIGN, 1).
%% --------------------------------------------------
%% Serialize
%% Serialized a Fate data value into a sequence of bytes
%% according to the Fate serialization specification.
%% TODO: The type Fate Data is not final yet.
-spec serialize(aefa_data:fate_type()) -> binary().
serialize(?FATE_TRUE) -> <<?TRUE>>;
serialize(?FATE_FALSE) -> <<?FALSE>>;
serialize(?FATE_NIL) -> <<?NIL>>; %% ! Untyped
serialize(?FATE_UNIT) -> <<?EMPTY_TUPLE>>; %% ! Untyped
serialize(M) when ?IS_FATE_MAP(M), ?FATE_MAP_SIZE(M) =:= 0 -> <<?EMPTY_MAP>>; %% ! Untyped
serialize(?FATE_EMPTY_STRING) -> <<?EMPTY_STRING>>;
serialize(I) when ?IS_FATE_INTEGER(I) -> serialize_integer(I);
serialize(?FATE_BITS(Bits)) when is_integer(Bits) -> serialize_bits(Bits);
serialize(String) when ?IS_FATE_STRING(String),
?FATE_STRING_SIZE(String) > 0,
?FATE_STRING_SIZE(String) < ?SHORT_STRING_SIZE ->
Size = ?FATE_STRING_SIZE(String),
Bytes = ?FATE_STRING_VALUE(String),
<<Size:6, ?SHORT_STRING:2, Bytes/binary>>;
serialize(String) when ?IS_FATE_STRING(String),
?FATE_STRING_SIZE(String) > 0,
?FATE_STRING_SIZE(String) >= ?SHORT_STRING_SIZE ->
Bytes = ?FATE_STRING_VALUE(String),
<<?LONG_STRING, (ae_rlp:encode(Bytes))/binary>>;
serialize(?FATE_ADDRESS(Address)) when is_binary(Address) ->
<<?ADDRESS, (ae_rlp:encode(Address))/binary>>;
serialize(?FATE_TUPLE(T)) when size(T) > 0 ->
S = size(T),
L = tuple_to_list(T),
Rest = << <<(serialize(E))/binary>> || E <- L >>,
if S < ?SHORT_TUPLE_SIZE ->
<<S:4, ?SHORT_TUPLE:4, Rest/binary>>;
true ->
Size = rlp_integer(S - ?SHORT_TUPLE_SIZE),
<<?LONG_TUPLE:8, Size/binary, Rest/binary>>
end;
serialize(L) when ?IS_FATE_LIST(L) ->
[_E|_] = List = ?FATE_LIST_VALUE(L),
S = length(List),
Rest = << <<(serialize(El))/binary>> || El <- List >>,
if S < ?SHORT_LIST_SIZE ->
<<S:4, ?SHORT_LIST:4, Rest/binary>>;
true ->
Val = rlp_integer(S - ?SHORT_LIST_SIZE),
<<?LONG_LIST, Val/binary, Rest/binary>>
end;
serialize(Map) when ?IS_FATE_MAP(Map) ->
L = [{_K,_V}|_] = maps:to_list(?FATE_MAP_VALUE(Map)),
Size = length(L),
%% TODO: check all K same type, and all V same type
%% check K =/= map
Elements = << <<(serialize(K1))/binary, (serialize(V1))/binary>> || {K1,V1} <- L >>,
<<?MAP,
(rlp_integer(Size))/binary,
(Elements)/binary>>;
serialize(?FATE_VARIANT(Size, Tag, Values)) when 0 =< Size
, Size < 256
, 0 =< Tag
, Tag < Size ->
<<?VARIANT, Size:8, Tag:8,
(serialize(?FATE_TUPLE(Values)))/binary
>>.
%% -----------------------------------------------------
rlp_integer(S) when S >= 0 ->
ae_rlp:encode(binary:encode_unsigned(S)).
serialize_integer(I) when ?IS_FATE_INTEGER(I) ->
V = ?FATE_INTEGER_VALUE(I),
Abs = abs(V),
Sign = case V < 0 of
true -> ?NEG_SIGN;
false -> ?POS_SIGN
end,
if Abs < ?SMALL_INT_SIZE -> <<Sign:1, Abs:6, ?SMALL_INT:1>>;
Sign =:= ?NEG_SIGN -> <<?NEG_BIG_INT,
(rlp_integer(Abs - ?SMALL_INT_SIZE))/binary>>;
Sign =:= ?POS_SIGN -> <<?POS_BIG_INT,
(rlp_integer(Abs - ?SMALL_INT_SIZE))/binary>>
end.
serialize_bits(B) when is_integer(B) ->
Abs = abs(B),
Sign = case B < 0 of
true -> ?NEG_SIGN;
false -> ?POS_SIGN
end,
if
Sign =:= ?NEG_SIGN -> <<?NEG_BITS, (rlp_integer(Abs))/binary>>;
Sign =:= ?POS_SIGN -> <<?POS_BITS, (rlp_integer(Abs))/binary>>
end.
-spec deserialize(binary()) -> aefa_data:fate_type().
deserialize(B) ->
{T, <<>>} = deserialize2(B),
T.
deserialize_one(B) -> deserialize2(B).
deserialize2(<<?POS_SIGN:1, I:6, ?SMALL_INT:1, Rest/binary>>) ->
{?MAKE_FATE_INTEGER(I), Rest};
deserialize2(<<?NEG_SIGN:1, I:6, ?SMALL_INT:1, Rest/binary>>) ->
{?MAKE_FATE_INTEGER(-I), Rest};
deserialize2(<<?NEG_BIG_INT, Rest/binary>>) ->
{Bint, Rest2} = ae_rlp:decode_one(Rest),
{?MAKE_FATE_INTEGER(-binary:decode_unsigned(Bint) - ?SMALL_INT_SIZE),
Rest2};
deserialize2(<<?POS_BIG_INT, Rest/binary>>) ->
{Bint, Rest2} = ae_rlp:decode_one(Rest),
{?MAKE_FATE_INTEGER(binary:decode_unsigned(Bint) + ?SMALL_INT_SIZE),
Rest2};
deserialize2(<<?NEG_BITS, Rest/binary>>) ->
{Bint, Rest2} = ae_rlp:decode_one(Rest),
{?FATE_BITS(-binary:decode_unsigned(Bint)), Rest2};
deserialize2(<<?POS_BITS, Rest/binary>>) ->
{Bint, Rest2} = ae_rlp:decode_one(Rest),
{?FATE_BITS(binary:decode_unsigned(Bint)), Rest2};
deserialize2(<<?LONG_STRING, Rest/binary>>) ->
{String, Rest2} = ae_rlp:decode_one(Rest),
{?MAKE_FATE_STRING(String), Rest2};
deserialize2(<<S:6, ?SHORT_STRING:2, Rest/binary>>) ->
String = binary:part(Rest, 0, S),
Rest2 = binary:part(Rest, byte_size(Rest), - (byte_size(Rest) - S)),
{?MAKE_FATE_STRING(String), Rest2};
deserialize2(<<?ADDRESS, Rest/binary>>) ->
{A, Rest2} = ae_rlp:decode_one(Rest),
{?FATE_ADDRESS(A), Rest2};
deserialize2(<<?TRUE, Rest/binary>>) ->
{?FATE_TRUE, Rest};
deserialize2(<<?FALSE, Rest/binary>>) ->
{?FATE_FALSE, Rest};
deserialize2(<<?NIL, Rest/binary>>) ->
{?FATE_NIL, Rest};
deserialize2(<<?EMPTY_TUPLE, Rest/binary>>) ->
{?FATE_UNIT, Rest};
deserialize2(<<?EMPTY_MAP, Rest/binary>>) ->
{?MAKE_FATE_MAP(#{}), Rest};
deserialize2(<<?EMPTY_STRING, Rest/binary>>) ->
{?FATE_EMPTY_STRING, Rest};
deserialize2(<<?LONG_TUPLE, Rest/binary>>) ->
{BSize, Rest1} = ae_rlp:decode_one(Rest),
N = binary:decode_unsigned(BSize) + ?SHORT_TUPLE_SIZE,
{List, Rest2} = deserialize_elements(N, Rest1),
{?FATE_TUPLE(list_to_tuple(List)), Rest2};
deserialize2(<<S:4, ?SHORT_TUPLE:4, Rest/binary>>) ->
{List, Rest1} = deserialize_elements(S, Rest),
{?FATE_TUPLE(list_to_tuple(List)), Rest1};
deserialize2(<<?LONG_LIST, Rest/binary>>) ->
{BLength, Rest1} = ae_rlp:decode_one(Rest),
Length = binary:decode_unsigned(BLength) + ?SHORT_LIST_SIZE,
{List, Rest2} = deserialize_elements(Length, Rest1),
{?MAKE_FATE_LIST(List), Rest2};
deserialize2(<<S:4, ?SHORT_LIST:4, Rest/binary>>) ->
{List, Rest1} = deserialize_elements(S, Rest),
{?MAKE_FATE_LIST(List), Rest1};
deserialize2(<<?MAP, Rest/binary>>) ->
{BSize, Rest1} = ae_rlp:decode_one(Rest),
Size = binary:decode_unsigned(BSize),
{List, Rest2} = deserialize_elements(2*Size, Rest1),
Map = insert_kv(List, #{}),
{?MAKE_FATE_MAP(Map), Rest2};
deserialize2(<<?VARIANT, Size:8, Tag:8, Rest/binary>>) ->
if Tag > Size -> exit({too_large_tag_in_variant, Tag, Size});
true ->
{?FATE_TUPLE(T), Rest2} = deserialize2(Rest),
{?FATE_VARIANT(Size, Tag, T), Rest2}
end.
insert_kv([], M) -> M;
insert_kv([K,V|R], M) -> insert_kv(R, maps:put(K, V, M)).
deserialize_elements(0, Rest) ->
{[], Rest};
deserialize_elements(N, Es) ->
{E, Rest} = deserialize2(Es),
{Tail, Rest2} = deserialize_elements(N-1, Rest),
{[E|Tail], Rest2}.

View File

@ -31,6 +31,7 @@ mnemonic(?JUMP) -> 'JUMP' ;
mnemonic(?INC) -> 'INC' ;
mnemonic(?CALL) -> 'CALL' ;
mnemonic(?CALL_T) -> 'CALL_T' ;
mnemonic(?CALL_R) -> 'CALL_R' ;
mnemonic(OP) -> {OP, nothandled} ;
mnemonic({comment,_}) -> 'COMMENT' .
@ -42,6 +43,7 @@ m_to_op('JUMP') -> ?JUMP ;
m_to_op('INC') -> ?INC ;
m_to_op('CALL') -> ?CALL ;
m_to_op('CALL_T') -> ?CALL_T ;
m_to_op('CALL_R') -> ?CALL_R ;
m_to_op(Data) when 0=<Data, Data=<255 -> Data.
args(?NOP) -> 0;
@ -49,13 +51,15 @@ args(?RETURN) -> 0;
args(?PUSH) -> 1;
args(?JUMP) -> 1;
args(?INC) -> 0;
args(?CALL) -> hash;
args(?CALL_T) -> hash;
args(?CALL) -> 1;
args(?CALL_T) -> 1;
args(?CALL_R) -> 2;
args(_) -> 0. %% TODO do not allow this
end_bb(?RETURN) -> true;
end_bb(?JUMP) -> true;
end_bb(?CALL) -> true;
end_bb(?CALL_T) -> true;
end_bb(?CALL_R) -> true;
end_bb(_) -> false.