diff --git a/.gitignore b/.gitignore index cccd365..1887839 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ aefateasm include/aeb_fate_opcodes.hrl src/aeb_fate_code.erl src/aeb_fate_opcodes.erl +src/aeb_fate_pp.erl diff --git a/Makefile b/Makefile index 071e04c..916426e 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ - - +GENERATED_SRC = src/aeb_fate_opcodes.erl src/aeb_fate_code.erl include/aeb_fate_opcodes.hrl src/aeb_fate_asm_scan.xrl src/aeb_fate_pp.erl +GENERATOR_DEPS = ebin/aeb_fate_generate_ops.beam src/aeb_fate_asm_scan.template REBAR ?= rebar3 all: local -local: src/aeb_fate_opcodes.erl src/aeb_fate_code.erl include/aeb_fate_opcodes.hrl src/aeb_fate_asm_scan.xrl +local: $(GENERATED_SRC) @$(REBAR) as local release console: local @@ -12,15 +12,12 @@ console: local clean: @$(REBAR) clean - rm -f src/aeb_fate_opcodes.erl - rm -f src/aeb_fate_code.erl - rm -f include/aeb_fate_opcodes.hrl + rm -f $(GENERATED_SRC) + rm -f ebin/* dialyzer: local @$(REBAR) as local dialyzer - - distclean: clean @rm -rf _build/ @@ -30,12 +27,8 @@ eunit: local test: local @$(REBAR) as local eunit - -ebin/aeb_fate_generate_ops.beam: src/aeb_fate_generate_ops.erl ebin +ebin/%.beam: src/%.erl erlc -o $(dir $@) $< -src/aeb_fate_opcodes.erl src/aeb_fate_code.erl include/aeb_fate_opcodes.hrl src/aeb_fate_asm_scan.xrl: ebin/aeb_fate_generate_ops.beam +$(GENERATED_SRC): $(GENERATOR_DEPS) erl -pa ebin/ -noshell -s aeb_fate_generate_ops gen_and_halt src/ include/ - -ebin: - mkdir ebin diff --git a/ebin/.gitkeep b/ebin/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/rebar.config b/rebar.config index 08234fd..2b014b7 100644 --- a/rebar.config +++ b/rebar.config @@ -45,5 +45,8 @@ "robocopy \"%REBAR_BUILD_DIR%/bin/\" ./ aefateasm* " "/njs /njh /nfl /ndl & exit /b 0"} % silence things ]} - ]}]}. - + ]}, + {eqc, [{erl_opts, [{parse_transform, eqc_cover}]}, + {extra_src_dirs, ["quickcheck"]} %% May not be called eqc! + ]} + ]}. diff --git a/src/aeb_fate_asm.erl b/src/aeb_fate_asm.erl index 5e320ef..5606894 100644 --- a/src/aeb_fate_asm.erl +++ b/src/aeb_fate_asm.erl @@ -2,6 +2,21 @@ %%% @copyright (C) 2019, Aeternity Anstalt %%% @doc Assembler for Fate machine code. %%% +%%% Fate code exists in 3 formats: +%%% +%%% 1. Fate byte code. This format is under consensus. +%%% 2. Fate assembler. This is a text represenation of fate code. +%%% This is not under consensus and other +%%% implemenation and toolchains could have +%%% their own format. +%%% 3. Internal. This is an Erlang representation of fate code +%%% Used by this particular engin implementation. +%%% +%%% This library handles all tree representations. +%%% The byte code format is described in a separate document. +%%% The internal format is described in a separate document. +%%% The text representation is described here: +%%% %%% Assembler code can be read from a file. %%% The assembler has the following format %%% Comments start with 2 semicolons and runs till end of line @@ -33,14 +48,15 @@ %%% false %%% 5. Strings %%% "Hello" -%%% 6. Empty map +%%% 6. Map %%% {} +%%% { 1 => { "foo" => true, "bar" => false} %%% 7. Lists %%% [] %%% [1, 2] %%% 8. Bit field %%% <000> -%%% <1010> +%%% <1010 1010> %%% <> %%% !<> %%% 9. Tuples @@ -157,14 +173,6 @@ format_arg_types([T|Ts]) -> , ", " , format_arg_types(Ts)]. -format_arg({immediate, I}) -> - aeb_fate_data:format(I); -format_arg({arg, N}) -> io_lib:format("arg~p", [N]); -format_arg({var, N}) -> io_lib:format("var~p", [N]); -format_arg({stack, 0}) -> "a"; -format_arg({stack, N}) -> io_lib:format("a~p", [N]). - - format_type(T) -> %% TODO: Limit to ok types. io_lib:format("~p", [T]). @@ -180,157 +188,10 @@ format_code([], _) -> ""; format_code([Op|Rest], Symbols) -> [" ", - format_op(Op, Symbols), + aeb_fate_pp:format_op(Op, Symbols), "\n", format_code(Rest, Symbols)]. -format_op('RETURN', _) -> "RETURN"; -format_op({'RETURNR', Arg}, _) -> ["RETURNR ", format_arg(Arg)]; -format_op({'CALL', {immediate, Function}}, Symbols) -> - ["CALL ", lookup(Function, Symbols)]; -format_op({'CALL_T', {immediate, Function}}, Symbols) -> - ["CALL_T ", lookup(Function, Symbols)]; -format_op({'CALL_R', {immediate, Contract}, {immediate, Function}}, Symbols) -> - ["CALL_R ", lookup(Contract, Symbols), "." , lookup(Function, Symbols)]; -format_op({'CALL_R', Contract, {immediate, Function}}, Symbols) -> - ["CALL_R ", format_arg(Contract), "." , lookup(Function, Symbols)]; -format_op({'CALL_TR', {immediate, Contract}, {immediate, Function}}, Symbols) -> - ["CALL_TR ", lookup(Contract, Symbols), "." , lookup(Function, Symbols)]; -format_op({'CALL_TR', Contract, {immediate, Function}}, Symbols) -> - ["CALL_TR ", format_arg(Contract), "." , lookup(Function, Symbols)]; -format_op({'JUMP', {immediate, BB}}, _) -> - ["JUMP ", io_lib:format("~p", [BB])]; -format_op({'JUMPIF', Arg, {immediate, BB}}, _) -> - ["JUMPIF ", format_arg(Arg), " ", io_lib:format("~p", [BB])]; -format_op({'SWITCH_V2', Variant, {immediate, BB1}, {immediate, BB2}}, _) -> - ["SWITCH_V2 ", format_arg(Variant), " ", BB1, " ", BB2]; -format_op({'SWITCH_V3', Variant, {immediate, BB1}, {immediate, BB2}, {immediate, BB3}}, _) -> - ["SWITCH_V2 ", format_arg(Variant), " ", BB1, " ", BB2, " ", BB3]; -format_op({'SWITCH_VN', Variant, BBs}, _) -> - ["SWITCH_VN ", format_arg(Variant), [[" ", BB] || {immedate, BB} <- BBs]]; -format_op({'PUSH', Arg0}, _) -> - ["PUSH ", format_arg(Arg0)]; -format_op('INCA', _) -> "INCA"; -format_op({'INC', Name}, _) -> ["INC ", format_arg(Name)]; -format_op({'DEC', Name}, _) -> ["DEC ", format_arg(Name)]; -format_op('DECA', _) -> "DECA"; -format_op({'ADD', Dest, Left, Right}, _) -> - ["ADD ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; -format_op({'SUB', Dest, Left, Right}, _) -> - ["SUB ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; -format_op({'MUL', Dest, Left, Right}, _) -> - ["MUL ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; -format_op({'DIV', Dest, Left, Right}, _) -> - ["DIV ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; -format_op({'MOD', Dest, Left, Right}, _) -> - ["MOD ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; -format_op({'POW', Dest, Left, Right}, _) -> - ["POW ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; -format_op({'LT', Dest, Left, Right}, _) -> - ["LT ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; -format_op({'GT', Dest, Left, Right}, _) -> - ["GT ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; -format_op({'ELT', Dest, Left, Right}, _) -> - ["ELT ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; -format_op({'EGT', Dest, Left, Right}, _) -> - ["EGT ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; -format_op({'EQ', Dest, Left, Right}, _) -> - ["EQ ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; -format_op({'NEQ', Dest, Left, Right}, _) -> - ["NEQ ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; -format_op({'AND', Dest, Left, Right}, _) -> - ["AND ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; -format_op({'OR', Dest, Left, Right}, _) -> - ["OR ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; -format_op({'NOT', Dest, Name}, _) -> - ["NOT ", format_arg(Dest), " ", format_arg(Name)]; -format_op({'TUPLE', {immediate, Size}}, _) -> - ["TUPLE ", io_lib:format("~p", [Size])]; -format_op({'ELEMENT', Type, Dest, Which, Tuple}, _) -> - [ "ELEMENT " - , io_lib:format("~p ", [Type]) - , format_arg(Dest), " " - , format_arg(Which), " " - , format_arg(Tuple)]; -format_op({'MAP_EMPTY', Dest}, _) -> - ["MAP_EMPTY ", format_arg(Dest)]; -format_op({'MAP_LOOKUP', Dest, Map, Key}, _) -> - ["MAP_LOOKUP ", format_arg(Dest), " " - , format_arg(Map), " ", format_arg(Key)]; -format_op({'MAP_DELETE', Dest, Map, Key}, _) -> - ["MAP_DELETE ", format_arg(Dest), " " - , format_arg(Map), " ", format_arg(Key)]; -format_op({'MAP_LOOKUPD', Dest, Map, Key, Default}, _) -> - ["MAP_LOOKUPD ", format_arg(Dest), " " - , format_arg(Map), " ", format_arg(Key), " ", format_arg(Default)]; -format_op({'MAP_UPDATE', Dest, Map, Key, Value}, _) -> - ["MAP_UPDATE ", format_arg(Dest), " " - , format_arg(Map), " ", format_arg(Key), " ", format_arg(Value)]; -format_op({'MAP_MEMBER', Dest, Map, Key}, _) -> - ["MAP_MEMBER ", format_arg(Dest), " " - , format_arg(Map), " ", format_arg(Key)]; -format_op({'MAP_FROM_LIST', Dest, List}, _) -> - ["MAP_FROM_LIST ", format_arg(Dest), " ", format_arg(List)]; -format_op({'NIL', Dest}, _) -> - ["NIL ", format_arg(Dest)]; -format_op({'IS_NIL', Dest, List}, _) -> - ["IS_NIL ", format_arg(Dest), " ", format_arg(List)]; -format_op({'CONS', Dest, Hd, Tl}, _) -> - ["CONS ", format_arg(Dest), " ", format_arg(Hd), " ", format_arg(Tl)]; -format_op({'HD', Dest, List}, _) -> - ["HD ", format_arg(Dest), " ", format_arg(List)]; -format_op({'TL', Dest, List}, _) -> - ["TL ", format_arg(Dest), " ", format_arg(List)]; -format_op({'LENGTH', Dest, List}, _) -> - ["LENGTH ", format_arg(Dest), " ", format_arg(List)]; -format_op({'STR_EQ', Dest, Str1, Str2}, _) -> - ["STR_EQ ", format_arg(Dest), " ", format_arg(Str1), format_arg(Str2)]; -format_op({'STR_JOIN', Dest, Str1, Str2}, _) -> - ["STR_JOIN ", format_arg(Dest), " ", format_arg(Str1), format_arg(Str2)]; -format_op({'INT_TO_STR', Dest, Str}, _) -> - ["INT_TO_STR ", format_arg(Dest), " ", format_arg(Str)]; -format_op({'ADDR_TO_STR', Dest, Str}, _) -> - ["ADDR_TO_STR ", format_arg(Dest), " ", format_arg(Str)]; -format_op({'STR_REVERSE', Dest, Str}, _) -> - ["STR_REVERSE ", format_arg(Dest), " ", format_arg(Str)]; -format_op({'INT_TO_ADDR', Dest, Str}, _) -> - ["INT_TO_ADDR ", format_arg(Dest), " ", format_arg(Str)]; -format_op({'VARIANT_TEST', Dest, Variant, Tag}, _) -> - ["VARIANT_TEST ", format_arg(Dest), " ", format_arg(Variant), " ", format_arg(Tag)]; -format_op({'VARIANT_ELEMENT', Dest, Variant, Index}, _) -> - ["VARIANT_ELEMENT ", format_arg(Dest), " ", format_arg(Variant), " ", format_arg(Index)]; -format_op({'VARIANT', Dest, SizeA, TagA, ElementsA}, _) -> - ["VARIANT ", format_arg(Dest), " ", format_arg(SizeA), " " - , format_arg(TagA), " ", format_arg(ElementsA)]; -format_op('BITS_NONEA', _) -> "BITS_NONEA "; -format_op({'BITS_NONE', To}, _) -> ["BITS_NONE ", format_arg(To)]; -format_op('BITS_ALLA', _) -> "BITS_ALLA"; -format_op({'BITS_ALL', To}, _) -> ["BITS_ALL ", format_arg(To)]; -format_op({'BITS_ALL_N', To, N}, _) -> - ["BITS_ALL_N ", format_arg(To), " ", format_arg(N)]; -format_op({'BITS_SET', To, Bits, Bit}, _) -> - ["BITS_SET ", format_arg(To), " ", format_arg(Bits), " ", format_arg(Bit)]; -format_op({'BITS_CLEAR', To, Bits, Bit}, _) -> - ["BITS_CLEAR ", format_arg(To), " ", format_arg(Bits), " ", format_arg(Bit)]; -format_op({'BITS_TEST', To, Bits, Bit}, _) -> - ["BITS_TEST ", format_arg(To), " ", format_arg(Bits), " ", format_arg(Bit)]; -format_op({'BITS_SUM', To, Bits}, _) -> - ["BITS_SUM ", format_arg(To), " ", format_arg(Bits)]; -format_op({'BITS_OR', To, Bits, Bit}, _) -> - ["BITS_OR ", format_arg(To), " ", format_arg(Bits), " ", format_arg(Bit)]; -format_op({'BITS_AND', To, Bits, Bit}, _) -> - ["BITS_AND ", format_arg(To), " ", format_arg(Bits), " ", format_arg(Bit)]; -format_op({'BITS_DIFF', To, Bits, Bit}, _) -> - ["BITS_DIFF ", format_arg(To), " ", format_arg(Bits), " ", format_arg(Bit)]; -format_op('DUPA', _) -> "DUPA"; -format_op({'DUP', {immediate, N}}, _) -> - ["DUP ", io_lib:format("~p", [N])]; -format_op({'POP', Dest}, _) -> - ["POP ", format_arg(Dest)]; -format_op({'STORE', Var, What}, _) -> - ["STORE ", format_arg(Var), " ", format_arg(What)]; -format_op('NOP', _) -> "NOP". - read_file(Filename) -> {ok, File} = file:read_file(Filename), @@ -709,7 +570,11 @@ deserialize_type(<<5, Rest/binary>>) -> {bits, Rest}; deserialize_type(<<6, Rest/binary>>) -> {K, Rest2} = deserialize_type(Rest), {V, Rest3} = deserialize_type(Rest2), - {{map, K, V}, Rest3}. + {{map, K, V}, Rest3}; +deserialize_type(<<7, Rest/binary>>) -> + {string, Rest}. + + deserialize_types(0, Binary, Acc) -> {lists:reverse(Acc), Binary}; @@ -756,6 +621,19 @@ to_bytecode([{hash,_line, Hash}|Rest], Address, Env, Code, Opts) -> to_bytecode([{id,_line, ID}|Rest], Address, Env, Code, Opts) -> {Hash, Env2} = insert_symbol(ID, Env), to_bytecode(Rest, Address, Env2, [{immediate, Hash}|Code], Opts); +to_bytecode([{'{',_line}|Rest], Address, Env, Code, Opts) -> + {Map, Rest2} = parse_map(Rest), + to_bytecode(Rest2, Address, Env, [{immediate, Map}|Code], Opts); +to_bytecode([{'[',_line}|Rest], Address, Env, Code, Opts) -> + {List, Rest2} = parse_list(Rest), + to_bytecode(Rest2, Address, Env, [{immediate, List}|Code], Opts); +to_bytecode([{'(',_line}|Rest], Address, Env, Code, Opts) -> + {Elements, Rest2} = parse_tuple(Rest), + Tuple = aeb_fate_data:make_tuple(list_to_tuple(Elements)), + to_bytecode(Rest2, Address, Env, [{immediate, Tuple}|Code], Opts); +to_bytecode([{bits,_line, Bits}|Rest], Address, Env, Code, Opts) -> + to_bytecode(Rest, Address, Env, [{immediate, Bits}|Code], Opts); + to_bytecode([{comment, Line, Comment}|Rest], Address, Env, Code, Opts) -> Env2 = insert_annotation(comment, Line, Comment, Env), to_bytecode(Rest, Address, Env2, Code, Opts); @@ -772,6 +650,51 @@ to_bytecode([], Address, Env, Code, Opts) -> end, Env2. +parse_map([{'}',_line}|Rest]) -> + {#{}, Rest}; +parse_map(Tokens) -> + {Key, [{arrow, _} | Rest]} = parse_value(Tokens), + {Value, Rest2} = parse_value(Rest), + case Rest2 of + [{',',_} | Rest3] -> + {Map, Rest4} = parse_map(Rest3), + {Map#{Key => Value}, Rest4}; + [{'}',_} | Rest3] -> + {#{Key => Value}, Rest3} + end. + +parse_list([{']',_line}|Rest]) -> + {[], Rest}; +parse_list(Tokens) -> + {Head , Rest} = parse_value(Tokens), + case Rest of + [{',',_} | Rest2] -> + {Tail, Rest3} = parse_list(Rest2), + {[Head | Tail], Rest3}; + [{']',_} | Rest3] -> + {[Head], Rest3} + end. + +parse_tuple([{')',_line}|Rest]) -> + {[], Rest}; +parse_tuple(Tokens) -> + {Head , Rest} = parse_value(Tokens), + case Rest of + [{',',_} | Rest2] -> + {Tail, Rest3} = parse_tuple(Rest2), + {[Head | Tail], Rest3}; + [{')',_} | Rest3] -> + {[Head], Rest3} + end. + + +parse_value([{int,_line, Int} | Rest]) -> {Int, Rest}; +parse_value([{boolean,_line, Bool} | Rest]) -> {Bool, Rest}; +parse_value([{hash,_line, Hash} | Rest]) -> {Hash, Rest}; +parse_value([{'{',_line} | Rest]) -> parse_map(Rest); +parse_value([{'[',_line} | Rest]) -> parse_list(Rest); +parse_value([{'(',_line} | Rest]) -> parse_tuple(Rest). + to_fun_def([{id, _, Name}, {'(', _} | Rest]) -> {ArgsType, [{'to', _} | Rest2]} = to_arg_types(Rest), @@ -829,9 +752,11 @@ serialize_type({tuple, Ts}) -> N when N =< 255 -> [3, N | [serialize_type(T) || T <- Ts]] end; -serialize_type(address) -> 4; -serialize_type(bits) -> 5; -serialize_type({map, K, V}) -> [6 | serialize_type(K) ++ serialize_type(V)]. +serialize_type(address) -> [4]; +serialize_type(bits) -> [5]; +serialize_type({map, K, V}) -> [6 | serialize_type(K) ++ serialize_type(V)]; +serialize_type(string) -> [7]. + %% ------------------------------------------------------------------- diff --git a/src/aeb_fate_asm_scan.template b/src/aeb_fate_asm_scan.template index f894aa5..fa4d49d 100644 --- a/src/aeb_fate_asm_scan.template +++ b/src/aeb_fate_asm_scan.template @@ -17,7 +17,8 @@ HEX = 0x{HEXDIGIT}+ HASH = #{HEXDIGIT}+ WS = [\000-\s] ID = {LOWER}[a-zA-Z0-9_]* - +STRING = "[^"]*" +BITS = (\!)?\<[\s01]*\> Rules. arg{INT} : {token, {arg, TokenLine, parse_arg(TokenChars)}}. @@ -38,13 +39,22 @@ FUNCTION : {token, {function, TokenLine, 'FUNCTION' }}. {token, {int, TokenLine, parse_hex(TokenChars)}}. {INT} : {token, {int, TokenLine, parse_int(TokenChars)}}. +-{INT} : + {token, {int, TokenLine, parse_int(TokenChars)}}. {HASH} : {token, {hash, TokenLine, parse_hash(TokenChars)}}. +{STRING} : + {token, {hash, TokenLine, list_to_binary(TokenChars)}}. +{BITS} : + {token, {bits, TokenLine, bits(TokenChars)}}. %% Symbols -\-\> : {token, {'to', TokenLine}}. -\: : {token, {'to', TokenLine}}. +\-\> : {token, {to, TokenLine}}. +\: : {token, {to, TokenLine}}. + +\=\> : {token, {arrow, TokenLine}}. + , : {token, {',', TokenLine}}. \( : {token, {'(', TokenLine}}. \) : {token, {')', TokenLine}}. @@ -97,3 +107,13 @@ scan(S) -> drop_prefix(C, [C|Rest]) -> drop_prefix(C, Rest); drop_prefix(_, Tail) -> Tail. + +bits([$!, $< | Rest]) -> + bits(Rest, -1); +bits([$< | Rest]) -> + bits(Rest, 0). + +bits([$> |_Rest], Acc) -> Acc; +bits([$0 | Rest], Acc) -> bits(Rest, Acc bsl 1); +bits([$1 | Rest], Acc) -> bits(Rest, (Acc bsl 1) bor 1); +bits([$ | Rest], Acc) -> bits(Rest, Acc). diff --git a/src/aeb_fate_data.erl b/src/aeb_fate_data.erl index f0a6898..3037d64 100644 --- a/src/aeb_fate_data.erl +++ b/src/aeb_fate_data.erl @@ -23,7 +23,7 @@ | {map, fate_type(), fate_type()} | {tuple, [fate_type()]} | address - | bits + | bits | {variant, integer()}. @@ -133,30 +133,25 @@ 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_UNIT) -> "()"; format(?FATE_TUPLE(T)) -> - "{ " ++ [format(E) ++ " " || E <- erlang:tuple_to_list(T)] ++ "}"; + ["( ", lists:join(", ", [ 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)] - ++ " )"; + ["(| ", + lists:join("| ", [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)) -> address_to_base58(Address); + ["{ ", format_kvs(maps:to_list(?FATE_MAP_VALUE(M))), " }"]; +format(?FATE_ADDRESS(Address)) -> ["#", address_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_list(List) -> + ["[ ", lists:join(", ", [format(E) || E <- List]), " ]"]. -format_kvs([]) -> ""; -format_kvs([{K,V}]) -> "( " ++ format(K) ++ " => " ++ format(V) ++ " )"; -format_kvs([{K,V} | Rest]) -> - "( " ++ format(K) ++ " => " ++ format(V) ++ " ), " ++ format_kvs(Rest). +format_kvs(List) -> + lists:join(", ", [ [format(K), " => ", format(V)] || {K, V} <- List]). %% -- Local base 58 library diff --git a/src/aeb_fate_encoding.erl b/src/aeb_fate_encoding.erl index 1fcbd9e..d0e5c12 100644 --- a/src/aeb_fate_encoding.erl +++ b/src/aeb_fate_encoding.erl @@ -128,7 +128,7 @@ serialize(L) when ?IS_FATE_LIST(L) -> <> end; serialize(Map) when ?IS_FATE_MAP(Map) -> - L = [{_K,_V}|_] = maps:to_list(?FATE_MAP_VALUE(Map)), + L = [{_K,_V}|_] = lists:sort(maps:to_list(?FATE_MAP_VALUE(Map))), Size = length(L), %% TODO: check all K same type, and all V same type %% check K =/= map @@ -136,10 +136,8 @@ serialize(Map) when ?IS_FATE_MAP(Map) -> <>; -serialize(?FATE_VARIANT(Size, Tag, Values)) when 0 =< Size - , Size < 256 - , 0 =< Tag - , Tag < Size -> +serialize(?FATE_VARIANT(Size, Tag, Values)) when 0 < Size, Size < 256, + 0 =< Tag, Tag < Size -> <>. diff --git a/src/aeb_fate_generate_ops.erl b/src/aeb_fate_generate_ops.erl index 3eb7def..5cfe13a 100644 --- a/src/aeb_fate_generate_ops.erl +++ b/src/aeb_fate_generate_ops.erl @@ -18,7 +18,8 @@ generate(Src, Include) -> generate_header_file(HrlFile, Ops), generate_opcodes_ops(aeb_fate_opcodes, HrlFile, Src, Ops), generate_code_ops(aeb_fate_code, Src, Ops), - generate_scanner("aeb_fate_asm_scan.template", "aeb_fate_asm_scan.xrl", Src, Ops). + generate_scanner("aeb_fate_asm_scan.template", "aeb_fate_asm_scan.xrl", Src, Ops), + gen_asm_pp(aeb_fate_pp, Src, Ops). %% TODO: Some real gas numbers... ops_defs() -> @@ -398,5 +399,103 @@ insert_tokens_in_template(<<"###REPLACEWITHNOTE###", Rest/binary >>, Tokens) -> insert_tokens_in_template(<>, Tokens) -> [B|insert_tokens_in_template(Rest, Tokens)]. +gen_asm_pp(Module, Path, Ops) -> + Filename = filename:join(Path, atom_to_list(Module)) ++ ".erl", + {ok, File} = file:open(Filename, [write]), + Formats = lists:flatten([gen_format(Op)++"\n" || Op <- Ops]), + io:format(File, "~s", [prelude(" Provide pretty printing functuions for " + "Fate instructions.\n")]), + io:format(File, "-module(~w).\n\n", [Module]), + io:format(File, + "-export([format_op/2]).\n\n" + "format_arg(t, T) ->\n" + " io_lib:format(\"~~p \", [T]);\n" + "format_arg(li, List) ->\n" + " [[\" \", E] || {immedate, E} <- List];\n" + "format_arg(_, {immediate, I}) ->\n" + " aeb_fate_data:format(I);\n" + "format_arg(a, {arg, N}) -> io_lib:format(\"arg~~p\", [N]);\n" + "format_arg(a, {var, N}) -> io_lib:format(\"var~~p\", [N]);\n" + "format_arg(a, {stack, 0}) -> \"a\";\n" + "format_arg(a, {stack, N}) -> io_lib:format(\"a~~p\", [N]).\n\n" + "lookup(Name, Symbols) ->\n" + " maps:get(Name, Symbols, Name).\n\n" + "~s" + , [Formats]), + io:format(File, "format_op(Op, _Symbols) -> io_lib:format(\";; Bad Op: ~~w\\n\", [Op]).\n", []), + file:close(File). + +gen_format(#{opname := Name}) when ('CALL' =:= Name) or (Name =:= 'CALL_T') -> + io_lib:format("format_op({~w, {immediate, Function}}, Symbols) ->\n" + "[\"~s \", lookup(Function, Symbols)];", + [Name, atom_to_list(Name)]); +gen_format(#{opname := Name}) when (Name =:= 'CALL_R') or (Name =:= 'CALL_TR') -> + io_lib:format("format_op({~w, {immediate, Contract}, {immediate, Function}}, Symbols) ->\n" + "[\"~s \", lookup(Contract, Symbols), \".\", lookup(Function, Symbols)];\n" + "format_op({~w, Contract, {immediate, Function}}, Symbols) ->\n" + "[\"~s \", format_arg(a, Contract), \".\", lookup(Function, Symbols)];", + [Name, atom_to_list(Name), Name, atom_to_list(Name)]); +gen_format(#{opname := Name, format := atomic}) -> + io_lib:format("format_op(~w, _) -> [\"~s\"];", [Name, atom_to_list(Name)]); +gen_format(#{opname := Name, format := Args}) -> + NameAsString = atom_to_list(Name), + case Args of + [T0] -> + io_lib:format( + "format_op({~w, Arg0}, _) ->\n" + " [\"~s \", format_arg(~w, Arg0)];", + [Name, NameAsString, T0]); + [T0, T1] -> + io_lib:format( + "format_op({~w, Arg0, Arg1}, _) ->\n" + " [\"~s \", format_arg(~w, Arg0), " + "\" \", format_arg(~w, Arg1)];", + [Name, NameAsString, T0, T1]); + [T0, T1, T2] -> + io_lib:format( + "format_op({~w, Arg0, Arg1, Arg2}, _) ->\n" + " [\"~s \", format_arg(~w, Arg0), " + "\" \", format_arg(~w, Arg1)," + "\" \", format_arg(~w, Arg2)];", + [Name, NameAsString, T0, T1, T2]); + [T0, T1, T2, T3] -> + io_lib:format( + "format_op({~w, Arg0, Arg1, Arg2, Arg3}, _) ->\n" + " [\"~s \", format_arg(~w, Arg0), " + "\" \", format_arg(~w, Arg1)," + "\" \", format_arg(~w, Arg2)," + "\" \", format_arg(~w, Arg3)];", + [Name, NameAsString, T0, T1, T2, T3]); + [T0, T1, T2, T3, T4] -> + io_lib:format( + "format_op({~w, Arg0, Arg1, Arg2, Arg3, Arg4}, _) ->\n" + " [\"~s \", format_arg(~w, Arg0), " + "\" \", format_arg(~w, Arg1)," + "\" \", format_arg(~w, Arg2)," + "\" \", format_arg(~w, Arg3)," + "\" \", format_arg(~w, Arg4)];", + [Name, NameAsString, T0, T1, T2, T3, T4]); + [T0, T1, T2, T3, T4, T5] -> + io_lib:format( + "format_op({~w, Arg0, Arg1, Arg2, Arg3, Arg4, Arg5}, _) ->\n" + " [\"~s \", format_arg(~w, Arg0), " + "\" \", format_arg(~w, Arg1)," + "\" \", format_arg(~w, Arg2)," + "\" \", format_arg(~w, Arg3)," + "\" \", format_arg(~w, Arg4)," + "\" \", format_arg(~w, Arg5)];", + [Name, NameAsString, T0, T1, T2, T3, T4, T5]); + [T0, T1, T2, T3, T4, T5, T6] -> + io_lib:format( + "format_op({~w, Arg0, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6}, _) ->\n" + " [\"~s \", format_arg(~w, Arg0), " + "\" \", format_arg(~w, Arg1)," + "\" \", format_arg(~w, Arg2)," + "\" \", format_arg(~w, Arg3)," + "\" \", format_arg(~w, Arg4)," + "\" \", format_arg(~w, Arg5)," + "\" \", format_arg(~w, Arg6)];", + [Name, NameAsString, T0, T1, T2, T3, T4, T5, T6]) + end. diff --git a/test/aeb_fate_asm_test.erl b/test/aeb_fate_asm_test.erl index dd32af6..fc20dbd 100644 --- a/test/aeb_fate_asm_test.erl +++ b/test/aeb_fate_asm_test.erl @@ -50,6 +50,8 @@ sources() -> , "remote" , "test" , "tuple" + , "mapofmap" + , "immediates" ]. check_roundtrip(File) -> @@ -62,4 +64,5 @@ check_roundtrip(File) -> {_Env2, ByteCode2} = assemble(DissasmCode), Code1 = aeb_fate_asm:strip(ByteCode), Code2 = aeb_fate_asm:strip(ByteCode2), + io:format("~s~n", [aeb_fate_asm:to_asm(disassemble(ByteCode2))]), ?assertEqual(Code1, Code2). diff --git a/test/asm_code/immediates.fate b/test/asm_code/immediates.fate new file mode 100644 index 0000000..d3a0077 --- /dev/null +++ b/test/asm_code/immediates.fate @@ -0,0 +1,68 @@ +;; CONTRACT immediates + +FUNCTION integer() : integer + RETURNR 42 + +FUNCTION neg_integer() : integer + RETURNR -2374683271468723648732648736498712634876147 + +FUNCTION hex_integer() : integer + RETURNR 0x0deadbeef0 + +FUNCTION bool() : boolean + RETURNR true + +FUNCTION bool_f() : boolean + RETURNR false + +FUNCTION string() : string + RETURNR "Hello" + +FUNCTION map() : {map, integer, boolean} + RETURNR {} + +FUNCTION map2() : {map, integer, boolean} + RETURNR {1 => true} + +FUNCTION map3() : {map, integer, boolean} + RETURNR {1 => true, + 2 => false} + +FUNCTION map4() : {map, integer, {map, string, boolean}} + RETURNR {1 => { "foo" => true, "bar" => false}, + 2 => {}, + 3 => { "foo" => false}} + +FUNCTION nil() : {list, integer} + RETURNR [] + +FUNCTION list1() : {list, integer} + RETURNR [1] + +FUNCTION list2() : {list, integer} + RETURNR [1, 2] + + +FUNCTION no_bits() : bits + RETURNR <> + +FUNCTION all_bits() : bits + RETURNR !<> + +FUNCTION some_bits() : bits + RETURNR <101010> + +FUNCTION many_bits() : bits + RETURNR !<010101> + +FUNCTION group_bits() : bits + RETURNR <1010 1010 0011 1001> + +FUNCTION unit() : {tuple, []} + RETURNR () + +FUNCTION tuple() : {tuple, [integer, boolean, string, {tuple, [integer, integer]}]} + RETURNR (42, true, "FooBar", (1, 2)) + + + diff --git a/test/asm_code/mapofmap.fate b/test/asm_code/mapofmap.fate new file mode 100644 index 0000000..bbda554 --- /dev/null +++ b/test/asm_code/mapofmap.fate @@ -0,0 +1,7 @@ +;; CONTRACT mapofmap +FUNCTION map() : {map, integer, {map, string, boolean}} + RETURNR {1 => { "foo" => true, "bar" => false}, + 2 => {}, + 3 => { "foo" => false}} + +