Handle argument and data serialization/deserialization.
This commit is contained in:
parent
25319fccd4
commit
23ed6caf64
55
include/aefa_data.hrl
Normal file
55
include/aefa_data.hrl
Normal 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
91
src/ae_rlp.erl
Normal 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)].
|
116
src/aefa_asm.erl
116
src/aefa_asm.erl
@ -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
|
||||
|
@ -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
180
src/aefa_data.erl
Normal 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
262
src/aefa_encoding.erl
Normal 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}.
|
@ -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.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user