diff --git a/include/aefa_data.hrl b/include/aefa_data.hrl new file mode 100644 index 0000000..424c9b1 --- /dev/null +++ b/include/aefa_data.hrl @@ -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). diff --git a/src/ae_rlp.erl b/src/ae_rlp.erl new file mode 100644 index 0000000..6e537b4 --- /dev/null +++ b/src/ae_rlp.erl @@ -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(<> = 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 + <>. + +-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(<>) when X =< ?UNTAGGED_LIMIT -> + %% Untagged value + {<>, B}; +decode_one(<> = B) when L < ?LIST_OFFSET -> + %% Byte array + {Size, Rest} = decode_size(B, ?BYTE_ARRAY_OFFSET), + <> = Rest, + {X, Tail}; +decode_one(<<_/binary>> = B) -> + %% List + {Size, Rest} = decode_size(B, ?LIST_OFFSET), + <> = Rest, + {decode_list(X), Tail}. + +decode_size(<>, 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(<>, Offset) -> + %% Actual size is in a byte array. + BinSize = L - Offset - ?UNTAGGED_SIZE_LIMIT, + <> = B, + {Size, Rest}. + +decode_list(<<>>) -> []; +decode_list(B) -> + {Element, Rest} = decode_one(B), + [Element|decode_list(Rest)]. diff --git a/src/aefa_asm.erl b/src/aefa_asm.erl index afefe04..50f9718 100644 --- a/src/aefa_asm.erl +++ b/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. - <> = Rest, - {Rest2, [Arg, OpName | Code]}; - hash -> - <> = Rest, - Code2 = [<>, OpName | Code], - {Rest2, Code2} + 1 -> + <> = Rest, + {Arg, Rest3} = aefa_encoding:deserialize_one(Rest2), + Modifier = bits_to_modifier(ArgType), + {Rest3, [{OpName, {Modifier, Arg}} | Code]}; + 2 -> + <> = 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 diff --git a/src/aefa_asm_scan.xrl b/src/aefa_asm_scan.xrl index c252cdf..50a2575 100644 --- a/src/aefa_asm_scan.xrl +++ b/src/aefa_asm_scan.xrl @@ -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), <>. diff --git a/src/aefa_data.erl b/src/aefa_data.erl new file mode 100644 index 0000000..568dbd7 --- /dev/null +++ b/src/aefa_data.erl @@ -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 = <>, 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, 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 = <>, + 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]). diff --git a/src/aefa_encoding.erl b/src/aefa_encoding.erl new file mode 100644 index 0000000..dd83e64 --- /dev/null +++ b/src/aefa_encoding.erl @@ -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) -> <>; +serialize(?FATE_FALSE) -> <>; +serialize(?FATE_NIL) -> <>; %% ! Untyped +serialize(?FATE_UNIT) -> <>; %% ! Untyped +serialize(M) when ?IS_FATE_MAP(M), ?FATE_MAP_SIZE(M) =:= 0 -> <>; %% ! Untyped +serialize(?FATE_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), + <>; +serialize(String) when ?IS_FATE_STRING(String), + ?FATE_STRING_SIZE(String) > 0, + ?FATE_STRING_SIZE(String) >= ?SHORT_STRING_SIZE -> + Bytes = ?FATE_STRING_VALUE(String), + <>; +serialize(?FATE_ADDRESS(Address)) when is_binary(Address) -> + <>; +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 -> + <>; + true -> + Size = rlp_integer(S - ?SHORT_TUPLE_SIZE), + <> + 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 -> + <>; + true -> + Val = rlp_integer(S - ?SHORT_LIST_SIZE), + <> + 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 >>, + <>; +serialize(?FATE_VARIANT(Size, Tag, Values)) when 0 =< Size + , Size < 256 + , 0 =< Tag + , Tag < Size -> + <>. + + +%% ----------------------------------------------------- + +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 =:= ?NEG_SIGN -> <>; + Sign =:= ?POS_SIGN -> <> + 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 -> <>; + Sign =:= ?POS_SIGN -> <> + end. + +-spec deserialize(binary()) -> aefa_data:fate_type(). +deserialize(B) -> + {T, <<>>} = deserialize2(B), + T. + +deserialize_one(B) -> deserialize2(B). + +deserialize2(<>) -> + {?MAKE_FATE_INTEGER(I), Rest}; +deserialize2(<>) -> + {?MAKE_FATE_INTEGER(-I), Rest}; +deserialize2(<>) -> + {Bint, Rest2} = ae_rlp:decode_one(Rest), + {?MAKE_FATE_INTEGER(-binary:decode_unsigned(Bint) - ?SMALL_INT_SIZE), + Rest2}; +deserialize2(<>) -> + {Bint, Rest2} = ae_rlp:decode_one(Rest), + {?MAKE_FATE_INTEGER(binary:decode_unsigned(Bint) + ?SMALL_INT_SIZE), + Rest2}; +deserialize2(<>) -> + {Bint, Rest2} = ae_rlp:decode_one(Rest), + {?FATE_BITS(-binary:decode_unsigned(Bint)), Rest2}; +deserialize2(<>) -> + {Bint, Rest2} = ae_rlp:decode_one(Rest), + {?FATE_BITS(binary:decode_unsigned(Bint)), Rest2}; +deserialize2(<>) -> + {String, Rest2} = ae_rlp:decode_one(Rest), + {?MAKE_FATE_STRING(String), Rest2}; +deserialize2(<>) -> + String = binary:part(Rest, 0, S), + Rest2 = binary:part(Rest, byte_size(Rest), - (byte_size(Rest) - S)), + {?MAKE_FATE_STRING(String), Rest2}; +deserialize2(<>) -> + {A, Rest2} = ae_rlp:decode_one(Rest), + {?FATE_ADDRESS(A), Rest2}; +deserialize2(<>) -> + {?FATE_TRUE, Rest}; +deserialize2(<>) -> + {?FATE_FALSE, Rest}; +deserialize2(<>) -> + {?FATE_NIL, Rest}; +deserialize2(<>) -> + {?FATE_UNIT, Rest}; +deserialize2(<>) -> + {?MAKE_FATE_MAP(#{}), Rest}; +deserialize2(<>) -> + {?FATE_EMPTY_STRING, Rest}; +deserialize2(<>) -> + {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(<>) -> + {List, Rest1} = deserialize_elements(S, Rest), + {?FATE_TUPLE(list_to_tuple(List)), Rest1}; +deserialize2(<>) -> + {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(<>) -> + {List, Rest1} = deserialize_elements(S, Rest), + {?MAKE_FATE_LIST(List), Rest1}; +deserialize2(<>) -> + {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(<>) -> + 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}. diff --git a/src/aefa_opcodes.erl b/src/aefa_opcodes.erl index 48d45f3..e1d26ea 100644 --- a/src/aefa_opcodes.erl +++ b/src/aefa_opcodes.erl @@ -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. 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.