Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d056dc620 | |||
| a51a864059 | |||
| 899bff9111 | |||
| b5daedaf95 | |||
| 8dd8e89c1e | |||
| dd1a6a9c3d | |||
| 161b5a6106 | |||
| 268208ec98 | |||
| 9411a131fc | |||
| c624f4956c | |||
| ab150ce7f8 | |||
| c85af9e7f3 | |||
| 01ae99f7e8 | |||
| a35307f61b | |||
| d04a827f05 | |||
| 7e26912bf9 | |||
| 8ba5f9e2b1 | |||
| 3c056db0b5 | |||
| 5e9d34849f | |||
| c0f2ac3163 | |||
| dae2dbeed6 | |||
| ce33ba8818 | |||
| 3ddae5e674 | |||
| 7b671d2187 | |||
| 16644ded72 | |||
| 36f910aff4 | |||
| 08e169c3b2 | |||
| 18eb37a8c5 | |||
| b95827b2d0 | |||
| afdb78b933 | |||
| c5a9878bd9 | |||
| e6623bd252 | |||
| 37f97e3837 | |||
| a539378405 |
@@ -19,16 +19,16 @@ jobs:
|
|||||||
- dialyzer-cache-v1-
|
- dialyzer-cache-v1-
|
||||||
- run:
|
- run:
|
||||||
name: Build
|
name: Build
|
||||||
command: rebar3 compile
|
command: make
|
||||||
- run:
|
- run:
|
||||||
name: Static Analysis
|
name: Static Analysis
|
||||||
command: rebar3 dialyzer
|
command: make dialyzer
|
||||||
- run:
|
- run:
|
||||||
name: Eunit
|
name: Eunit
|
||||||
command: rebar3 eunit
|
command: make eunit
|
||||||
- run:
|
- run:
|
||||||
name: Common Tests
|
name: Common Tests
|
||||||
command: rebar3 ct
|
command: make test
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: dialyzer-cache-v1-{{ .Branch }}-{{ .Revision }}
|
key: dialyzer-cache-v1-{{ .Branch }}-{{ .Revision }}
|
||||||
paths:
|
paths:
|
||||||
|
|||||||
@@ -9,4 +9,10 @@ rel/example_project
|
|||||||
.concrete/DEV_MODE
|
.concrete/DEV_MODE
|
||||||
.rebar
|
.rebar
|
||||||
aeb_asm_scan.erl
|
aeb_asm_scan.erl
|
||||||
|
aeb_fate_asm_scan.erl
|
||||||
|
aeb_fate_asm_scan.xrl
|
||||||
_build/
|
_build/
|
||||||
|
aefateasm
|
||||||
|
include/aeb_fate_opcodes.hrl
|
||||||
|
src/aeb_fate_code.erl
|
||||||
|
src/aeb_fate_opcodes.erl
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
@$(REBAR) as local release
|
||||||
|
|
||||||
|
console: local
|
||||||
|
@$(REBAR) as local shell
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@$(REBAR) clean
|
||||||
|
rm -f src/aeb_fate_opcodes.erl
|
||||||
|
rm -f src/aeb_fate_code.erl
|
||||||
|
rm -f include/aeb_fate_opcodes.hrl
|
||||||
|
|
||||||
|
dialyzer: local
|
||||||
|
@$(REBAR) as local dialyzer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
distclean: clean
|
||||||
|
@rm -rf _build/
|
||||||
|
|
||||||
|
eunit: local
|
||||||
|
@$(REBAR) as local eunit
|
||||||
|
|
||||||
|
test: local
|
||||||
|
@$(REBAR) as local eunit
|
||||||
|
|
||||||
|
|
||||||
|
ebin/aeb_fate_generate_ops.beam: src/aeb_fate_generate_ops.erl ebin
|
||||||
|
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
|
||||||
|
erl -pa ebin/ -noshell -s aeb_fate_generate_ops gen_and_halt src/ include/
|
||||||
|
|
||||||
|
ebin:
|
||||||
|
mkdir ebin
|
||||||
@@ -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).
|
||||||
+43
-2
@@ -2,10 +2,51 @@
|
|||||||
|
|
||||||
{erl_opts, [debug_info]}.
|
{erl_opts, [debug_info]}.
|
||||||
|
|
||||||
{deps, []}.
|
{deps, [ {eblake2, "1.0.0"}
|
||||||
|
, {aeserialization, {git, "https://github.com/aeternity/aeserialization.git",
|
||||||
|
{ref, "b55c372"}}}
|
||||||
|
, {getopt, "1.0.1"}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
{escript_incl_apps, [aebytecode, eblake2, aeserialization, getopt]}.
|
||||||
|
{escript_main_app, aebytecode}.
|
||||||
|
{escript_name, aefateasm}.
|
||||||
|
{escript_emu_args, "%%!"}.
|
||||||
|
{provider_hooks, [{post, [{compile, escriptize}]}]}.
|
||||||
|
|
||||||
|
|
||||||
{dialyzer, [
|
{dialyzer, [
|
||||||
{warnings, [unknown]},
|
{warnings, [unknown]},
|
||||||
{plt_apps, all_deps},
|
{plt_apps, all_deps},
|
||||||
{base_plt_apps, [erts, kernel, stdlib]}
|
{base_plt_apps, [erts, kernel, stdlib, crypto, getopt]}
|
||||||
|
]}.
|
||||||
|
|
||||||
|
|
||||||
|
{relx, [{release, {aebytecode, "2.0.1"},
|
||||||
|
[aebytecode, eblake2, getopt]},
|
||||||
|
|
||||||
|
{dev_mode, true},
|
||||||
|
{include_erts, false},
|
||||||
|
|
||||||
|
{extended_start_script, true}]}.
|
||||||
|
|
||||||
|
{profiles, [{binary, [
|
||||||
|
{deps, [ {eblake2, "1.0.0"}
|
||||||
|
, {aeserialization, {git, "https://github.com/aeternity/aeserialization.git",
|
||||||
|
{ref, "b55c372"}}}
|
||||||
|
, {getopt, "1.0.1"}
|
||||||
|
]},
|
||||||
|
|
||||||
|
{post_hooks, [{"(linux|darwin|solaris|freebsd|netbsd|openbsd)",
|
||||||
|
escriptize,
|
||||||
|
"cp \"$REBAR_BUILD_DIR/bin/aefateasm\" ./aefateasm"},
|
||||||
|
{"win32",
|
||||||
|
escriptize,
|
||||||
|
"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!
|
||||||
|
]}
|
||||||
]}.
|
]}.
|
||||||
|
|||||||
+16
-1
@@ -1 +1,16 @@
|
|||||||
[].
|
{"1.1.0",
|
||||||
|
[{<<"aeserialization">>,
|
||||||
|
{git,"https://github.com/aeternity/aeserialization.git",
|
||||||
|
{ref,"b55c3726f4a21063721c68d6fa7fda39121edf11"}},
|
||||||
|
0},
|
||||||
|
{<<"base58">>,
|
||||||
|
{git,"https://github.com/aeternity/erl-base58.git",
|
||||||
|
{ref,"60a335668a60328a29f9731b67c4a0e9e3d50ab6"}},
|
||||||
|
1},
|
||||||
|
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0},
|
||||||
|
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}]}.
|
||||||
|
[
|
||||||
|
{pkg_hash,[
|
||||||
|
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
|
||||||
|
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
|
||||||
|
].
|
||||||
|
|||||||
@@ -0,0 +1,887 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% @copyright (C) 2019, Aeternity Anstalt
|
||||||
|
%%% @doc Assembler for Fate machine code.
|
||||||
|
%%%
|
||||||
|
%%% 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
|
||||||
|
%%% ;; This is a comment
|
||||||
|
%%% Opcode mnemonics start with an upper case letter.
|
||||||
|
%%% DUP
|
||||||
|
%%% Identifiers start with a lower case letter
|
||||||
|
%%% an_identifier
|
||||||
|
%%% References to function arguments start with arg followed by an integer
|
||||||
|
%%% arg0
|
||||||
|
%%% References to variables/registers start with var followed by an integer
|
||||||
|
%%% var0
|
||||||
|
%%% References to stack postions is either a (for stack 0)
|
||||||
|
%%% or start with stack followed by an integer
|
||||||
|
%%% stack1
|
||||||
|
%%% a
|
||||||
|
%%%
|
||||||
|
%%% Immediates can be of 9 types:
|
||||||
|
%%% 1. Integers
|
||||||
|
%%% 42
|
||||||
|
%%% -2374683271468723648732648736498712634876147
|
||||||
|
%%% 2. Hexadecimal integers starting with 0x
|
||||||
|
%%% 0x0deadbeef0
|
||||||
|
%%% 3. addresses, a 256-bit hash strings starting with #
|
||||||
|
%%% followed by up to 64 hex chars
|
||||||
|
%%% #00000deadbeef
|
||||||
|
%%% 4. Boolean
|
||||||
|
%%% true
|
||||||
|
%%% false
|
||||||
|
%%% 5. Strings
|
||||||
|
%%% "Hello"
|
||||||
|
%%% 6. Empty map
|
||||||
|
%%% {}
|
||||||
|
%%% 7. Lists
|
||||||
|
%%% []
|
||||||
|
%%% [1, 2]
|
||||||
|
%%% 8. Bit field
|
||||||
|
%%% <000>
|
||||||
|
%%% <1010>
|
||||||
|
%%% <>
|
||||||
|
%%% !<>
|
||||||
|
%%% 9. Tuples
|
||||||
|
%%% ()
|
||||||
|
%%% (1, "foo")
|
||||||
|
%%% @end
|
||||||
|
%%% Created : 21 Dec 2017
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(aeb_fate_asm).
|
||||||
|
|
||||||
|
-export([ assemble_file/3
|
||||||
|
, asm_to_bytecode/2
|
||||||
|
, bytecode_to_fate_code/2
|
||||||
|
, function_call/1
|
||||||
|
, pp/1
|
||||||
|
, read_file/1
|
||||||
|
, strip/1
|
||||||
|
, to_asm/1
|
||||||
|
, to_hexstring/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-include_lib("aebytecode/include/aeb_fate_opcodes.hrl").
|
||||||
|
-include_lib("aebytecode/include/aeb_fate_data.hrl").
|
||||||
|
-define(HASH_BYTES, 32).
|
||||||
|
|
||||||
|
assemble_file(InFile, OutFile, Options) ->
|
||||||
|
Asm = read_file(InFile),
|
||||||
|
{_Env, BC} = asm_to_bytecode(Asm, Options),
|
||||||
|
ok = file:write_file(OutFile, BC).
|
||||||
|
|
||||||
|
function_call(String) ->
|
||||||
|
{ok, Tokens, _} = aeb_fate_asm_scan:scan(String),
|
||||||
|
parse_function_call(Tokens).
|
||||||
|
|
||||||
|
parse_function_call([{id,_,Name}, {'(',_}| Rest]) ->
|
||||||
|
{Args, []} = to_args(Rest),
|
||||||
|
aeb_fate_encoding:serialize(
|
||||||
|
{tuple, {mk_hash(Name), {tuple, list_to_tuple(Args)}}}).
|
||||||
|
|
||||||
|
|
||||||
|
to_args([{')', _}]) -> {[], []};
|
||||||
|
to_args(Tokens) ->
|
||||||
|
case to_data(Tokens) of
|
||||||
|
{Arg, [{',', _} | Rest]} ->
|
||||||
|
{More, Rest2} = to_args(Rest),
|
||||||
|
{[Arg|More], Rest2};
|
||||||
|
{Arg, [{')', _} | Rest]} ->
|
||||||
|
{[Arg], Rest}
|
||||||
|
end.
|
||||||
|
|
||||||
|
to_data([{int,_line, Int}|Rest]) ->
|
||||||
|
{Int, Rest};
|
||||||
|
to_data([{boolean,_line, Bool}|Rest]) ->
|
||||||
|
{Bool, Rest};
|
||||||
|
to_data([{hash,_line, Hash}|Rest]) ->
|
||||||
|
{Hash, Rest}.
|
||||||
|
|
||||||
|
pp(FateCode) ->
|
||||||
|
Listing = to_asm(FateCode),
|
||||||
|
io_lib:format("~ts~n",[Listing]).
|
||||||
|
|
||||||
|
|
||||||
|
to_asm(#{ functions := Functions
|
||||||
|
, symbols := Symbols
|
||||||
|
, annotations := Annotations} = _FateCode) ->
|
||||||
|
insert_comments(get_comments(Annotations), 1,
|
||||||
|
lists:flatten(
|
||||||
|
io_lib:format("~s",
|
||||||
|
[format_functions(Functions, Symbols)]))).
|
||||||
|
|
||||||
|
insert_comments([{L,C}|Comments], L, String) ->
|
||||||
|
";; " ++ C ++ "\n" ++ insert_comments(Comments, L + 1, String);
|
||||||
|
insert_comments(Comments, L, [$\n|String]) ->
|
||||||
|
"\n" ++ insert_comments(Comments, L+1, String);
|
||||||
|
insert_comments(Comments, L, [C|Rest]) ->
|
||||||
|
[C|insert_comments(Comments, L, Rest)];
|
||||||
|
insert_comments([],_,[]) -> [];
|
||||||
|
insert_comments([{L,C}|Rest], _, []) ->
|
||||||
|
";; " ++ C ++ "\n" ++ insert_comments(Rest, L + 1, []).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
format_functions(Functions, Symbols) ->
|
||||||
|
[format(lookup(Name, Symbols),
|
||||||
|
Sig,
|
||||||
|
lists:sort(maps:to_list(CodeMap)),
|
||||||
|
Symbols)
|
||||||
|
||
|
||||||
|
{Name, {Sig, CodeMap}} <- maps:to_list(Functions)].
|
||||||
|
|
||||||
|
|
||||||
|
format(Name, Sig, BBs, Symbols) ->
|
||||||
|
[ "FUNCTION "
|
||||||
|
, Name
|
||||||
|
, format_sig(Sig)
|
||||||
|
, "\n"
|
||||||
|
, format_bbs(BBs, Symbols)].
|
||||||
|
|
||||||
|
format_sig({Args, RetType}) ->
|
||||||
|
[ "( "
|
||||||
|
, format_arg_types(Args)
|
||||||
|
, ") : "
|
||||||
|
, format_type(RetType)].
|
||||||
|
|
||||||
|
format_arg_types([]) -> "";
|
||||||
|
format_arg_types([T]) -> format_type(T);
|
||||||
|
format_arg_types([T|Ts]) ->
|
||||||
|
[format_type(T)
|
||||||
|
, ", "
|
||||||
|
, 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]).
|
||||||
|
|
||||||
|
format_bbs([], _) ->
|
||||||
|
[];
|
||||||
|
format_bbs([{BB, Code}|Rest], Symbols) ->
|
||||||
|
[ io_lib:format(" ;; BB : ~p~n", [BB])
|
||||||
|
, format_code(Code, Symbols)
|
||||||
|
| format_bbs(Rest, Symbols)].
|
||||||
|
|
||||||
|
format_code([], _) ->
|
||||||
|
"";
|
||||||
|
format_code([Op|Rest], Symbols) ->
|
||||||
|
[" ",
|
||||||
|
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),
|
||||||
|
binary_to_list(File).
|
||||||
|
|
||||||
|
asm_to_bytecode(AssemblerCode, Options) ->
|
||||||
|
{ok, Tokens, _} = aeb_fate_asm_scan:scan(AssemblerCode),
|
||||||
|
|
||||||
|
case proplists:lookup(pp_tokens, Options) of
|
||||||
|
{pp_tokens, true} ->
|
||||||
|
io:format("Tokens ~p~n",[Tokens]);
|
||||||
|
none ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
|
||||||
|
Env = to_bytecode(Tokens, none, #{ functions => #{}
|
||||||
|
, symbols => #{}
|
||||||
|
, annotations => #{}
|
||||||
|
}, [], Options),
|
||||||
|
|
||||||
|
ByteList = serialize(Env),
|
||||||
|
Signatures = serialize_sigs(Env),
|
||||||
|
SymbolTable = serialize_symbol_table(Env),
|
||||||
|
Annotatations = serialize_annotations(Env),
|
||||||
|
ByteCode = << (aeser_rlp:encode(list_to_binary(ByteList)))/binary,
|
||||||
|
(aeser_rlp:encode(list_to_binary(Signatures)))/binary,
|
||||||
|
(aeser_rlp:encode(SymbolTable))/binary,
|
||||||
|
(aeser_rlp:encode(Annotatations))/binary
|
||||||
|
>>,
|
||||||
|
|
||||||
|
case proplists:lookup(pp_hex_string, Options) of
|
||||||
|
{pp_hex_string, true} ->
|
||||||
|
io:format("Code: ~s~n",[to_hexstring(ByteList)]);
|
||||||
|
none ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
|
||||||
|
{Env, ByteCode}.
|
||||||
|
|
||||||
|
strip(ByteCode) ->
|
||||||
|
{Code, _Rest} = aeser_rlp:decode_one(ByteCode),
|
||||||
|
Code.
|
||||||
|
|
||||||
|
bytecode_to_fate_code(Bytes, _Options) ->
|
||||||
|
{ByteCode, Rest1} = aeser_rlp:decode_one(Bytes),
|
||||||
|
{Signatures, Rest2} = aeser_rlp:decode_one(Rest1),
|
||||||
|
{SymbolTable, Rest3} = aeser_rlp:decode_one(Rest2),
|
||||||
|
{Annotations, <<>>} = aeser_rlp:decode_one(Rest3),
|
||||||
|
|
||||||
|
Env1 = deserialize(ByteCode, #{ function => none
|
||||||
|
, bb => 0
|
||||||
|
, current_bb_code => []
|
||||||
|
, functions => #{}
|
||||||
|
, code => #{}
|
||||||
|
}),
|
||||||
|
Env2 = deserialize_signatures(Signatures, Env1),
|
||||||
|
Env3 = deserialize_symbols(SymbolTable, Env2),
|
||||||
|
Env4 = deserialize_annotations(Annotations, Env3),
|
||||||
|
Env4.
|
||||||
|
|
||||||
|
|
||||||
|
deserialize(<<?FUNCTION:8, A, B, C, D, Rest/binary>>,
|
||||||
|
#{ function := none
|
||||||
|
, bb := 0
|
||||||
|
, current_bb_code := []
|
||||||
|
} = Env) ->
|
||||||
|
{Sig, Rest2} = deserialize_signature(Rest),
|
||||||
|
Env2 = Env#{function => {<<A,B,C,D>>, Sig}},
|
||||||
|
deserialize(Rest2, Env2);
|
||||||
|
deserialize(<<?FUNCTION:8, A, B, C, D, Rest/binary>>,
|
||||||
|
#{ function := {F, Sig}
|
||||||
|
, bb := BB
|
||||||
|
, current_bb_code := Code
|
||||||
|
, code := Program
|
||||||
|
, functions := Funs} = Env) ->
|
||||||
|
{NewSig, Rest2} = deserialize_signature(Rest),
|
||||||
|
case Code of
|
||||||
|
[] ->
|
||||||
|
Env2 = Env#{ bb => 0
|
||||||
|
, current_bb_code => []
|
||||||
|
, function => {<<A,B,C,D>>, NewSig}
|
||||||
|
, code => #{}
|
||||||
|
, functions => Funs#{F => {Sig, Program}}},
|
||||||
|
deserialize(Rest2, Env2);
|
||||||
|
_ ->
|
||||||
|
Env2 = Env#{ bb => 0
|
||||||
|
, current_bb_code => []
|
||||||
|
, function => {<<A,B,C,D>>, NewSig}
|
||||||
|
, code => #{}
|
||||||
|
, functions =>
|
||||||
|
Funs#{F => {Sig,
|
||||||
|
Program#{ BB => lists:reverse(Code)}}}},
|
||||||
|
deserialize(Rest2, Env2)
|
||||||
|
end;
|
||||||
|
deserialize(<<Op:8, Rest/binary>>,
|
||||||
|
#{ bb := BB
|
||||||
|
, current_bb_code := Code
|
||||||
|
, code := Program} = Env) ->
|
||||||
|
{Rest2, OpCode} = deserialize_op(Op, Rest, Code),
|
||||||
|
case aeb_fate_opcodes:end_bb(Op) of
|
||||||
|
true ->
|
||||||
|
deserialize(Rest2, Env#{ bb => BB+1
|
||||||
|
, current_bb_code => []
|
||||||
|
, code => Program#{BB =>
|
||||||
|
lists:reverse(OpCode)}});
|
||||||
|
false ->
|
||||||
|
deserialize(Rest2, Env#{ current_bb_code => OpCode})
|
||||||
|
end;
|
||||||
|
deserialize(<<>>, #{ function := {F, Sig}
|
||||||
|
, bb := BB
|
||||||
|
, current_bb_code := Code
|
||||||
|
, code := Program
|
||||||
|
, functions := Funs} = Env) ->
|
||||||
|
FunctionCode =
|
||||||
|
case Code of
|
||||||
|
[] -> Program;
|
||||||
|
_ -> Program#{ BB => lists:reverse(Code)}
|
||||||
|
end,
|
||||||
|
Env#{ bb => 0
|
||||||
|
, current_bb_code => []
|
||||||
|
, function => none
|
||||||
|
, code => #{}
|
||||||
|
, functions => Funs#{F => {Sig, FunctionCode}}}.
|
||||||
|
|
||||||
|
deserialize_op(?ELEMENT, Rest, Code) ->
|
||||||
|
{Type, Rest2} = deserialize_type(Rest),
|
||||||
|
<<ArgType:8, Rest3/binary>> = Rest2,
|
||||||
|
{Arg0, Rest4} = aeb_fate_encoding:deserialize_one(Rest3),
|
||||||
|
{Arg1, Rest5} = aeb_fate_encoding:deserialize_one(Rest4),
|
||||||
|
{Arg2, Rest6} = aeb_fate_encoding:deserialize_one(Rest5),
|
||||||
|
Modifier0 = bits_to_modifier(ArgType band 2#11),
|
||||||
|
Modifier1 = bits_to_modifier((ArgType bsr 2) band 2#11),
|
||||||
|
Modifier2 = bits_to_modifier((ArgType bsr 4) band 2#11),
|
||||||
|
{Rest6, [{ aeb_fate_opcodes:mnemonic(?ELEMENT)
|
||||||
|
, Type
|
||||||
|
, {Modifier0, Arg0}
|
||||||
|
, {Modifier1, Arg1}
|
||||||
|
, {Modifier2, Arg2}}
|
||||||
|
| Code]};
|
||||||
|
deserialize_op(?SWITCH_VN, Rest, Code) ->
|
||||||
|
<<ArgType:8, Rest2/binary>> = Rest,
|
||||||
|
{Arg0, Rest3} = aeb_fate_encoding:deserialize_one(Rest2),
|
||||||
|
case aeb_fate_encoding:deserialize_one(Rest3) of
|
||||||
|
{N, Rest4} when is_integer(N), N >= 0 ->
|
||||||
|
Modifier0 = bits_to_modifier(ArgType band 2#11),
|
||||||
|
immediate = bits_to_modifier((ArgType bsr 2) band 2#11),
|
||||||
|
{BBs, Rest5} = deserialize_n(N, Rest4),
|
||||||
|
{Rest5, [{aeb_fate_opcodes:mnemonic(?SWITCH_VN)
|
||||||
|
, {Modifier0, Arg0}
|
||||||
|
, {immediate, N}
|
||||||
|
, list_to_tuple(BBs)}
|
||||||
|
| Code]};
|
||||||
|
_ -> exit(bad_argument_to_switch_vn)
|
||||||
|
end;
|
||||||
|
deserialize_op(Op, Rest, Code) ->
|
||||||
|
OpName = aeb_fate_opcodes:mnemonic(Op),
|
||||||
|
case aeb_fate_opcodes:args(Op) of
|
||||||
|
0 -> {Rest, [OpName | Code]};
|
||||||
|
1 ->
|
||||||
|
<<ArgType:8, Rest2/binary>> = Rest,
|
||||||
|
{Arg, Rest3} = aeb_fate_encoding:deserialize_one(Rest2),
|
||||||
|
Modifier = bits_to_modifier(ArgType),
|
||||||
|
{Rest3, [{OpName, {Modifier, Arg}} | Code]};
|
||||||
|
2 ->
|
||||||
|
<<ArgType:8, Rest2/binary>> = Rest,
|
||||||
|
{Arg0, Rest3} = aeb_fate_encoding:deserialize_one(Rest2),
|
||||||
|
{Arg1, Rest4} = aeb_fate_encoding:deserialize_one(Rest3),
|
||||||
|
Modifier0 = bits_to_modifier(ArgType band 2#11),
|
||||||
|
Modifier1 = bits_to_modifier((ArgType bsr 2) band 2#11),
|
||||||
|
{Rest4, [{OpName, {Modifier0, Arg0},
|
||||||
|
{Modifier1, Arg1}} | Code]};
|
||||||
|
3 ->
|
||||||
|
<<ArgType:8, Rest2/binary>> = Rest,
|
||||||
|
{Arg0, Rest3} = aeb_fate_encoding:deserialize_one(Rest2),
|
||||||
|
{Arg1, Rest4} = aeb_fate_encoding:deserialize_one(Rest3),
|
||||||
|
{Arg2, Rest5} = aeb_fate_encoding:deserialize_one(Rest4),
|
||||||
|
Modifier0 = bits_to_modifier(ArgType band 2#11),
|
||||||
|
Modifier1 = bits_to_modifier((ArgType bsr 2) band 2#11),
|
||||||
|
Modifier2 = bits_to_modifier((ArgType bsr 4) band 2#11),
|
||||||
|
{Rest5, [{ OpName
|
||||||
|
, {Modifier0, Arg0}
|
||||||
|
, {Modifier1, Arg1}
|
||||||
|
, {Modifier2, Arg2}}
|
||||||
|
| Code]};
|
||||||
|
4 ->
|
||||||
|
<<ArgType:8, Rest2/binary>> = Rest,
|
||||||
|
{Arg0, Rest3} = aeb_fate_encoding:deserialize_one(Rest2),
|
||||||
|
{Arg1, Rest4} = aeb_fate_encoding:deserialize_one(Rest3),
|
||||||
|
{Arg2, Rest5} = aeb_fate_encoding:deserialize_one(Rest4),
|
||||||
|
{Arg3, Rest6} = aeb_fate_encoding:deserialize_one(Rest5),
|
||||||
|
Modifier0 = bits_to_modifier(ArgType band 2#11),
|
||||||
|
Modifier1 = bits_to_modifier((ArgType bsr 2) band 2#11),
|
||||||
|
Modifier2 = bits_to_modifier((ArgType bsr 4) band 2#11),
|
||||||
|
Modifier3 = bits_to_modifier((ArgType bsr 6) band 2#11),
|
||||||
|
{Rest6, [{ OpName
|
||||||
|
, {Modifier0, Arg0}
|
||||||
|
, {Modifier1, Arg1}
|
||||||
|
, {Modifier2, Arg2}
|
||||||
|
, {Modifier3, Arg3}}
|
||||||
|
| Code]}
|
||||||
|
end.
|
||||||
|
|
||||||
|
deserialize_n(N, Binary) ->
|
||||||
|
deserialize_n(N, Binary, []).
|
||||||
|
|
||||||
|
deserialize_n(0, Binary, Acc) ->
|
||||||
|
{lists:reverse(Acc), Binary};
|
||||||
|
deserialize_n(N, Binary, Acc) ->
|
||||||
|
{Value, Rest} = aeb_fate_encoding:deserialize_one(Binary),
|
||||||
|
deserialize_n(N-1, Rest, [Value|Acc]).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
deserialize_signatures(_Signatures, Env) -> Env.
|
||||||
|
|
||||||
|
deserialize_symbols(Table, Env) ->
|
||||||
|
?FATE_MAP_VALUE(SymbolTable) = aeb_fate_encoding:deserialize(Table),
|
||||||
|
Env#{symbols => SymbolTable}.
|
||||||
|
|
||||||
|
deserialize_annotations(AnnotationsBin, Env) ->
|
||||||
|
?FATE_MAP_VALUE(Annotations) = aeb_fate_encoding:deserialize(AnnotationsBin),
|
||||||
|
Env#{annotations => Annotations}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
serialize_sigs(_Env) -> [].
|
||||||
|
|
||||||
|
serialize_symbol_table(#{ symbols := Symbols }) ->
|
||||||
|
aeb_fate_encoding:serialize(aeb_fate_data:make_map(Symbols)).
|
||||||
|
|
||||||
|
serialize_annotations(#{ annotations := Annotations}) ->
|
||||||
|
aeb_fate_encoding:serialize(aeb_fate_data:make_map(Annotations)).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
serialize(#{functions := Functions} =_Env) ->
|
||||||
|
%% Sort the functions oon name to get a canonical serialisation.
|
||||||
|
Code = [[?FUNCTION, Name, serialize_signature(Sig), C] ||
|
||||||
|
{Name, {Sig, C}} <- lists:sort(maps:to_list(Functions))],
|
||||||
|
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
|
||||||
|
|
||||||
|
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([ ?ELEMENT
|
||||||
|
, ResType
|
||||||
|
| Rest]) ->
|
||||||
|
[?ELEMENT,
|
||||||
|
serialize_type(ResType)
|
||||||
|
| serialize_code(Rest)];
|
||||||
|
serialize_code([ ?SWITCH_VN
|
||||||
|
, {Arg0Type, Arg0}
|
||||||
|
, {immediate, N}
|
||||||
|
| Rest]) when is_integer(N), N >= 0 ->
|
||||||
|
ArgSpec =
|
||||||
|
modifier_bits(Arg0Type) bor
|
||||||
|
(modifier_bits(immediate) bsl 2),
|
||||||
|
{Serialization, Rest2} = serialize_n_ints(N, Rest),
|
||||||
|
[?SWITCH_VN
|
||||||
|
, ArgSpec
|
||||||
|
, serialize_data(Arg0Type, Arg0)
|
||||||
|
, serialize_data(immediate, N)
|
||||||
|
| Serialization] ++ serialize_code(Rest2);
|
||||||
|
serialize_code([B|Rest]) ->
|
||||||
|
[B | serialize_code(Rest)];
|
||||||
|
serialize_code([]) -> [].
|
||||||
|
|
||||||
|
serialize_n_ints(N, Rest) ->
|
||||||
|
serialize_n_ints(N, Rest, []).
|
||||||
|
|
||||||
|
serialize_n_ints(0, Rest, Acc) ->
|
||||||
|
%% Acc is a list of binaries.
|
||||||
|
{lists:reverse(Acc), Rest};
|
||||||
|
serialize_n_ints(N, [Int|Rest], Acc) when is_integer(Int), Int >= 0 ->
|
||||||
|
serialize_n_ints(N - 1, Rest, [aeb_fate_encoding:serialize(Int)|Acc]);
|
||||||
|
serialize_n_ints(_, [], _) ->
|
||||||
|
exit(not_enough_bbs_for_switch_vn);
|
||||||
|
serialize_n_ints(_, _, _) ->
|
||||||
|
exit(bad_bbs_value_for_switch_vn).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% 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) ->
|
||||||
|
aeb_fate_encoding:serialize(Data).
|
||||||
|
|
||||||
|
serialize_signature({Args, RetType}) ->
|
||||||
|
[serialize_type({tuple, Args}) |
|
||||||
|
serialize_type(RetType)].
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
deserialize_signature(Binary) ->
|
||||||
|
{{tuple, Args}, Rest} = deserialize_type(Binary),
|
||||||
|
{RetType, Rest2} = deserialize_type(Rest),
|
||||||
|
{{Args, RetType}, Rest2}.
|
||||||
|
|
||||||
|
deserialize_type(<<0, Rest/binary>>) -> {integer, Rest};
|
||||||
|
deserialize_type(<<1, Rest/binary>>) -> {boolean, Rest};
|
||||||
|
deserialize_type(<<2, Rest/binary>>) ->
|
||||||
|
{T, Rest2} = deserialize_type(Rest),
|
||||||
|
{{list, T}, Rest2};
|
||||||
|
deserialize_type(<<3, N, Rest/binary>>) ->
|
||||||
|
{Ts, Rest2} = deserialize_types(N, Rest, []),
|
||||||
|
{{tuple, Ts}, Rest2};
|
||||||
|
deserialize_type(<<4, Rest/binary>>) -> {address, Rest};
|
||||||
|
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}.
|
||||||
|
|
||||||
|
deserialize_types(0, Binary, Acc) ->
|
||||||
|
{lists:reverse(Acc), Binary};
|
||||||
|
deserialize_types(N, Binary, Acc) ->
|
||||||
|
{T, Rest} = deserialize_type(Binary),
|
||||||
|
deserialize_types(N-1, Rest, [T | Acc]).
|
||||||
|
|
||||||
|
|
||||||
|
to_hexstring(ByteList) ->
|
||||||
|
"0x" ++ lists:flatten(
|
||||||
|
[io_lib:format("~2.16.0b", [X])
|
||||||
|
|| X <- ByteList]).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% -------------------------------------------------------------------
|
||||||
|
%% Parser
|
||||||
|
%% Asm tokens -> Fate code env
|
||||||
|
%% -------------------------------------------------------------------
|
||||||
|
|
||||||
|
to_bytecode([{function,_line, 'FUNCTION'}|Rest], Address, Env, Code, Opts) ->
|
||||||
|
Env2 = insert_fun(Address, Code, Env),
|
||||||
|
{Fun, Rest2} = to_fun_def(Rest),
|
||||||
|
to_bytecode(Rest2, Fun, Env2, [], Opts);
|
||||||
|
to_bytecode([{mnemonic,_line, 'ELEMENT'}|Rest], Address, Env, Code, Opts) ->
|
||||||
|
OpCode = aeb_fate_opcodes:m_to_op('ELEMENT'),
|
||||||
|
{RetType, Rest2} = to_type(Rest),
|
||||||
|
to_bytecode(Rest2, Address, Env, [RetType, OpCode|Code], Opts);
|
||||||
|
to_bytecode([{mnemonic,_line, Op}|Rest], Address, Env, Code, Opts) ->
|
||||||
|
OpCode = aeb_fate_opcodes:m_to_op(Op),
|
||||||
|
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, [{immediate, Int}|Code], Opts);
|
||||||
|
to_bytecode([{boolean,_line, Bool}|Rest], Address, Env, Code, Opts) ->
|
||||||
|
to_bytecode(Rest, Address, Env, [{immediate, Bool}|Code], Opts);
|
||||||
|
to_bytecode([{hash,_line, Hash}|Rest], Address, Env, Code, Opts) ->
|
||||||
|
to_bytecode(Rest, Address, Env, [{immediate, Hash}|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([{comment, Line, Comment}|Rest], Address, Env, Code, Opts) ->
|
||||||
|
Env2 = insert_annotation(comment, Line, Comment, Env),
|
||||||
|
to_bytecode(Rest, Address, Env2, Code, Opts);
|
||||||
|
|
||||||
|
to_bytecode([], Address, Env, Code, Opts) ->
|
||||||
|
Env2 = insert_fun(Address, Code, Env),
|
||||||
|
#{functions := Funs} = Env2,
|
||||||
|
case proplists:lookup(pp_opcodes, Opts) of
|
||||||
|
{pp_opcodes, true} ->
|
||||||
|
Ops = [C || {_Name, {_Sig, C}} <- maps:to_list(Funs)],
|
||||||
|
io:format("opcodes ~p~n", [Ops]);
|
||||||
|
none ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
Env2.
|
||||||
|
|
||||||
|
|
||||||
|
to_fun_def([{id, _, Name}, {'(', _} | Rest]) ->
|
||||||
|
{ArgsType, [{'to', _} | Rest2]} = to_arg_types(Rest),
|
||||||
|
{RetType, Rest3} = to_type(Rest2),
|
||||||
|
{{Name, ArgsType, RetType}, Rest3}.
|
||||||
|
|
||||||
|
to_arg_types([{')', _} | Rest]) -> {[], Rest};
|
||||||
|
to_arg_types(Tokens) ->
|
||||||
|
case to_type(Tokens) of
|
||||||
|
{Type, [{',', _} | Rest]} ->
|
||||||
|
{MoreTypes, Rest2} = to_arg_types(Rest),
|
||||||
|
{[Type|MoreTypes], Rest2};
|
||||||
|
{Type, [{')', _} | Rest]} ->
|
||||||
|
{[Type], Rest}
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
%% Type handling
|
||||||
|
|
||||||
|
to_type([{id, _, "integer"} | Rest]) -> {integer, Rest};
|
||||||
|
to_type([{id, _, "boolean"} | Rest]) -> {boolean, Rest};
|
||||||
|
to_type([{id, _, "string"} | Rest]) -> {string, Rest};
|
||||||
|
to_type([{id, _, "address"} | Rest]) -> {address, Rest};
|
||||||
|
to_type([{id, _, "bits"} | Rest]) -> {bits, Rest};
|
||||||
|
to_type([{'{', _}, {id, _, "list"}, {',', _} | Rest]) ->
|
||||||
|
%% TODO: Error handling
|
||||||
|
{ListType, [{'}', _}| Rest2]} = to_type(Rest),
|
||||||
|
{{list, ListType}, Rest2};
|
||||||
|
to_type([{'{', _}, {id, _, "tuple"}, {',', _}, {'[', _} | Rest]) ->
|
||||||
|
%% TODO: Error handling
|
||||||
|
{ElementTypes, [{'}', _}| Rest2]} = to_list_of_types(Rest),
|
||||||
|
{{tuple, ElementTypes}, Rest2};
|
||||||
|
to_type([{'{', _}, {id, _, "map"}, {',', _} | Rest]) ->
|
||||||
|
%% TODO: Error handling
|
||||||
|
{KeyType, [{',', _}| Rest2]} = to_type(Rest),
|
||||||
|
{ValueType, [{'}', _}| Rest3]} = to_type(Rest2),
|
||||||
|
{{map, KeyType, ValueType}, Rest3}.
|
||||||
|
|
||||||
|
to_list_of_types([{']', _} | Rest]) -> {[], Rest};
|
||||||
|
to_list_of_types(Tokens) ->
|
||||||
|
case to_type(Tokens) of
|
||||||
|
{Type, [{',', _} | Rest]} ->
|
||||||
|
{MoreTypes, Rest2} = to_list_of_types(Rest),
|
||||||
|
{[Type|MoreTypes], Rest2};
|
||||||
|
{Type, [{']', _} | Rest]} ->
|
||||||
|
{[Type], Rest}
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec serialize_type(aeb_fate_data:fate_type_type()) -> [byte()].
|
||||||
|
serialize_type(integer) -> [0];
|
||||||
|
serialize_type(boolean) -> [1];
|
||||||
|
serialize_type({list, T}) -> [2 | serialize_type(T)];
|
||||||
|
serialize_type({tuple, Ts}) ->
|
||||||
|
case length(Ts) of
|
||||||
|
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)].
|
||||||
|
|
||||||
|
|
||||||
|
%% -------------------------------------------------------------------
|
||||||
|
%% Helper functions
|
||||||
|
%% -------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% State handling
|
||||||
|
|
||||||
|
insert_fun(none, [], Env) -> Env;
|
||||||
|
insert_fun({Name, Type, RetType}, Code, #{functions := Functions} = Env) ->
|
||||||
|
{Hash, Env2} = insert_symbol(Name, Env),
|
||||||
|
Env2#{
|
||||||
|
functions => Functions#{Hash => {{Type, RetType}, lists:reverse(Code)}}
|
||||||
|
}.
|
||||||
|
|
||||||
|
mk_hash(Id) ->
|
||||||
|
%% Use first 4 bytes of blake hash
|
||||||
|
{ok, <<A:8, B:8, C:8, D:8,_/binary>> } = eblake2:blake2b(?HASH_BYTES, list_to_binary(Id)),
|
||||||
|
<<A,B,C,D>>.
|
||||||
|
|
||||||
|
%% Handle annotations
|
||||||
|
|
||||||
|
insert_annotation(comment, Line, Comment, #{annotations := A} = Env) ->
|
||||||
|
Key = aeb_fate_data:make_tuple({aeb_fate_data:make_string("comment"), Line}),
|
||||||
|
Value = aeb_fate_data:make_string(Comment),
|
||||||
|
Env#{annotations => A#{ Key => Value}}.
|
||||||
|
|
||||||
|
get_comments(Annotations) ->
|
||||||
|
[ {Line, Comment} ||
|
||||||
|
{?FATE_TUPLE({?FATE_STRING_VALUE("comment"), Line}),
|
||||||
|
?FATE_STRING_VALUE(Comment)} <- maps:to_list(Annotations)].
|
||||||
|
|
||||||
|
%% Symbols handling
|
||||||
|
|
||||||
|
insert_symbol(Id, Env) ->
|
||||||
|
Hash = mk_hash(Id),
|
||||||
|
insert_symbol(Id, Hash, Env).
|
||||||
|
|
||||||
|
insert_symbol(Id, Hash, #{symbols := Symbols} = Env) ->
|
||||||
|
case maps:find(Hash, Symbols) of
|
||||||
|
{ok, Id} -> {Hash, Env};
|
||||||
|
{ok, Id2} ->
|
||||||
|
%% Very unlikely...
|
||||||
|
exit({two_symbols_with_same_hash, Id, Id2});
|
||||||
|
error ->
|
||||||
|
{Hash, Env#{symbols => Symbols#{ Id => Hash
|
||||||
|
, Hash => Id}}}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% Symbol table handling
|
||||||
|
|
||||||
|
lookup(Name, Symbols) ->
|
||||||
|
maps:get(Name, Symbols, Name).
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% @copyright (C) 2019, aeternity Anstalt
|
||||||
|
%%% @doc
|
||||||
|
%%% Handling FATE code.
|
||||||
|
%%% @end
|
||||||
|
###REPLACEWITHNOTE###
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
|
||||||
|
Definitions.
|
||||||
|
DIGIT = [0-9]
|
||||||
|
HEXDIGIT = [0-9a-fA-F]
|
||||||
|
LOWER = [a-z_]
|
||||||
|
UPPER = [A-Z]
|
||||||
|
INT = {DIGIT}+
|
||||||
|
HEX = 0x{HEXDIGIT}+
|
||||||
|
HASH = #{HEXDIGIT}+
|
||||||
|
WS = [\000-\s]
|
||||||
|
ID = {LOWER}[a-zA-Z0-9_]*
|
||||||
|
|
||||||
|
|
||||||
|
Rules.
|
||||||
|
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)}}.
|
||||||
|
|
||||||
|
true : {token, {boolean, TokenLine, true}}.
|
||||||
|
false : {token, {boolean, TokenLine, false}}.
|
||||||
|
|
||||||
|
###REPLACEWITHOPTOKENS###
|
||||||
|
|
||||||
|
FUNCTION : {token, {function, TokenLine, 'FUNCTION' }}.
|
||||||
|
|
||||||
|
{ID} :
|
||||||
|
{token, {id, TokenLine, TokenChars}}.
|
||||||
|
{HEX} :
|
||||||
|
{token, {int, TokenLine, parse_hex(TokenChars)}}.
|
||||||
|
{INT} :
|
||||||
|
{token, {int, TokenLine, parse_int(TokenChars)}}.
|
||||||
|
{HASH} :
|
||||||
|
{token, {hash, TokenLine, parse_hash(TokenChars)}}.
|
||||||
|
|
||||||
|
|
||||||
|
%% Symbols
|
||||||
|
\-\> : {token, {'to', TokenLine}}.
|
||||||
|
\: : {token, {'to', TokenLine}}.
|
||||||
|
, : {token, {',', TokenLine}}.
|
||||||
|
\( : {token, {'(', TokenLine}}.
|
||||||
|
\) : {token, {')', TokenLine}}.
|
||||||
|
\[ : {token, {'[', TokenLine}}.
|
||||||
|
\] : {token, {']', TokenLine}}.
|
||||||
|
\{ : {token, {'{', TokenLine}}.
|
||||||
|
\} : {token, {'}', TokenLine}}.
|
||||||
|
|
||||||
|
;;.* :
|
||||||
|
{token, {comment, TokenLine, drop_prefix($;, TokenChars)}}.
|
||||||
|
|
||||||
|
\. : skip_token.
|
||||||
|
|
||||||
|
|
||||||
|
%% Whitespace ignore
|
||||||
|
{WS} : skip_token.
|
||||||
|
|
||||||
|
%% Comments (TODO: nested comments)
|
||||||
|
|
||||||
|
|
||||||
|
. : {error, "Unexpected token: " ++ TokenChars}.
|
||||||
|
|
||||||
|
Erlang code.
|
||||||
|
|
||||||
|
-export([scan/1]).
|
||||||
|
|
||||||
|
-dialyzer({nowarn_function, yyrev/2}).
|
||||||
|
|
||||||
|
-ignore_xref([format_error/1, string/2, token/2, token/3, tokens/2, tokens/3]).
|
||||||
|
|
||||||
|
-include_lib("aebytecode/include/aeb_fate_opcodes.hrl").
|
||||||
|
|
||||||
|
|
||||||
|
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>>.
|
||||||
|
|
||||||
|
scan(S) ->
|
||||||
|
string(S).
|
||||||
|
|
||||||
|
drop_prefix(C, [C|Rest]) ->
|
||||||
|
drop_prefix(C, Rest);
|
||||||
|
drop_prefix(_, Tail) -> Tail.
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
%% FATE data representation.
|
||||||
|
%%
|
||||||
|
-include("aeb_fate_data.hrl").
|
||||||
|
|
||||||
|
-module(aeb_fate_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_tuple() :: ?FATE_TUPLE_T.
|
||||||
|
|
||||||
|
-type fate_type_type() :: integer
|
||||||
|
| boolean
|
||||||
|
| {list, fate_type()}
|
||||||
|
| {map, fate_type(), fate_type()}
|
||||||
|
| {tuple, [fate_type()]}
|
||||||
|
| address
|
||||||
|
| bits
|
||||||
|
| {variant, integer()}.
|
||||||
|
|
||||||
|
|
||||||
|
-type fate_type() ::
|
||||||
|
fate_boolean()
|
||||||
|
| fate_integer()
|
||||||
|
| fate_nil()
|
||||||
|
| fate_list()
|
||||||
|
| fate_unit()
|
||||||
|
| fate_tuple()
|
||||||
|
| fate_string()
|
||||||
|
| fate_address()
|
||||||
|
| fate_variant()
|
||||||
|
| fate_map()
|
||||||
|
| fate_type_type().
|
||||||
|
|
||||||
|
-export_type([fate_type/0
|
||||||
|
, fate_boolean/0
|
||||||
|
, fate_integer/0
|
||||||
|
, fate_nil/0
|
||||||
|
, fate_list/0
|
||||||
|
, fate_unit/0
|
||||||
|
, fate_tuple/0
|
||||||
|
, fate_string/0
|
||||||
|
, fate_address/0
|
||||||
|
, fate_variant/0
|
||||||
|
, fate_map/0
|
||||||
|
, fate_type_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_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) -> "()";
|
||||||
|
format(?FATE_TUPLE(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)) ->
|
||||||
|
["(| ",
|
||||||
|
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(V) -> exit({not_a_fate_type, V}).
|
||||||
|
|
||||||
|
format_list(List) ->
|
||||||
|
["[ ", lists:join(", ", [format(E) || E <- List]), " ]"].
|
||||||
|
|
||||||
|
format_kvs(List) ->
|
||||||
|
lists:join(", ", [ [format(K), " => ", format(V)] || {K, V} <- List]).
|
||||||
|
|
||||||
|
|
||||||
|
%% -- 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.
|
||||||
|
|
||||||
|
address_to_base58(<<A:256>>) ->
|
||||||
|
integer_to_base58(A).
|
||||||
|
|
||||||
|
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]).
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
%% 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(aeb_fate_encoding).
|
||||||
|
|
||||||
|
-export([ deserialize/1
|
||||||
|
, deserialize_one/1
|
||||||
|
, serialize/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-include("aeb_fate_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(aeb_fate_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, (aeser_rlp:encode(Bytes))/binary>>;
|
||||||
|
serialize(?FATE_ADDRESS(Address)) when is_binary(Address) ->
|
||||||
|
<<?ADDRESS, (aeser_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 ->
|
||||||
|
aeser_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()) -> aeb_fate_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} = aeser_rlp:decode_one(Rest),
|
||||||
|
{?MAKE_FATE_INTEGER(-binary:decode_unsigned(Bint) - ?SMALL_INT_SIZE),
|
||||||
|
Rest2};
|
||||||
|
deserialize2(<<?POS_BIG_INT, Rest/binary>>) ->
|
||||||
|
{Bint, Rest2} = aeser_rlp:decode_one(Rest),
|
||||||
|
{?MAKE_FATE_INTEGER(binary:decode_unsigned(Bint) + ?SMALL_INT_SIZE),
|
||||||
|
Rest2};
|
||||||
|
deserialize2(<<?NEG_BITS, Rest/binary>>) ->
|
||||||
|
{Bint, Rest2} = aeser_rlp:decode_one(Rest),
|
||||||
|
{?FATE_BITS(-binary:decode_unsigned(Bint)), Rest2};
|
||||||
|
deserialize2(<<?POS_BITS, Rest/binary>>) ->
|
||||||
|
{Bint, Rest2} = aeser_rlp:decode_one(Rest),
|
||||||
|
{?FATE_BITS(binary:decode_unsigned(Bint)), Rest2};
|
||||||
|
deserialize2(<<?LONG_STRING, Rest/binary>>) ->
|
||||||
|
{String, Rest2} = aeser_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} = aeser_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} = aeser_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} = aeser_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} = aeser_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}.
|
||||||
@@ -0,0 +1,402 @@
|
|||||||
|
-module(aeb_fate_generate_ops).
|
||||||
|
|
||||||
|
-export([ gen_and_halt/1
|
||||||
|
, generate/0]).
|
||||||
|
|
||||||
|
gen_and_halt([SrcDirArg, IncludeDirArg]) ->
|
||||||
|
generate(atom_to_list(SrcDirArg),
|
||||||
|
atom_to_list(IncludeDirArg)),
|
||||||
|
halt().
|
||||||
|
|
||||||
|
generate() ->
|
||||||
|
generate("src/", "include/").
|
||||||
|
|
||||||
|
generate(Src, Include) ->
|
||||||
|
Ops = gen(ops_defs()),
|
||||||
|
%% io:format("ops: ~p\n", [Ops]),
|
||||||
|
HrlFile = Include ++ "aeb_fate_opcodes.hrl",
|
||||||
|
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).
|
||||||
|
|
||||||
|
%% TODO: Some real gas numbers...
|
||||||
|
ops_defs() ->
|
||||||
|
%% Opname, Opcode, args, end_bb, gas, format, Constructor, Documentation
|
||||||
|
[ { 'RETURN', 16#00, 0, true, 2, atomic, return, "Return from function call pop stack to arg0. The type of the retun value has to match the return type of the function."}
|
||||||
|
, { 'RETURNR', 16#01, 1, true, 2, [a], returnr, "Return from function call copy Arg0 to arg0. The type of the retun value has to match the return type of the function."}
|
||||||
|
, { 'CALL', 16#02, 1, true, 4, [is], call, "Call given function with args on stack. The types of the arguments has to match the argument typs of the function."}
|
||||||
|
, { 'CALL_R', 16#03, 2, true, 8, [a,is], call_r, "Remote call to given contract and function. The types of the arguments has to match the argument typs of the function."}
|
||||||
|
, { 'CALL_T', 16#04, 1, true, 4, [is], call_t, "Tail call to given function. The types of the arguments has to match the argument typs of the function. And the return type of the called function has to match the type of the current function."}
|
||||||
|
, { 'CALL_TR', 16#05, 2, true, 8, [a,is], call_tr, "Remote tail call to given contract and function. The types of the arguments has to match the argument typs of the function. And the return type of the called function has to match the type of the current function."}
|
||||||
|
, { 'JUMP', 16#06, 1, true, 3, [ii], jump, "Jump to a basic block. The basic block has to exist in the current function."}
|
||||||
|
, { 'JUMPIF', 16#07, 2, true, 4, [a,ii], jumpif, "Conditional jump to a basic block. If Arg0 then jump to Arg1."}
|
||||||
|
, { 'SWITCH_V2', 16#08, 3, true, 4, [a,ii,ii], switch, "Conditional jump to a basic block on variant tag."}
|
||||||
|
, { 'SWITCH_V3', 16#09, 4, true, 4, [a,ii,ii,ii], switch, "Conditional jump to a basic block on variant tag."}
|
||||||
|
, { 'SWITCH_VN', 16#0a, 2, true, 4, [a,li], switch, "Conditional jump to a basic block on variant tag."}
|
||||||
|
, { 'PUSH', 16#0b, 1, false, 2, [a], push, "Push argument to stack."}
|
||||||
|
, { 'DUPA', 16#0c, 0, false, 3, atomic, dup, "push copy of accumulator on stack."}
|
||||||
|
, { 'DUP', 16#0d, 1, false, 3, [a], dup, "push Arg0 stack pos on top of stack."}
|
||||||
|
, { 'POP', 16#0e, 1, false, 3, [a], pop, "Arg0 := top of stack."}
|
||||||
|
, { 'STORE', 16#0f, 2, false, 3, [a,a], store, "Arg0 := Arg1."}
|
||||||
|
, { 'INCA', 16#10, 0, false, 2, atomic, inc, "Increment accumulator."}
|
||||||
|
, { 'INC', 16#11, 1, false, 2, [a], inc, "Increment argument."}
|
||||||
|
, { 'DECA', 16#12, 0, false, 2, atomic, dec, "Decrement accumulator."}
|
||||||
|
, { 'DEC', 16#13, 1, false, 2, [a], dec, "Decrement argument."}
|
||||||
|
, { 'ADD', 16#14, 3, false, 3, [a,a,a], add, "Arg0 := Arg1 + Arg2."}
|
||||||
|
, { 'SUB', 16#15, 3, false, 3, [a,a,a], sub, "Arg0 := Arg1 - Arg2."}
|
||||||
|
, { 'MUL', 16#16, 3, false, 3, [a,a,a], mul, "Arg0 := Arg1 * Arg2."}
|
||||||
|
, { 'DIV', 16#17, 3, false, 3, [a,a,a], divide, "Arg0 := Arg1 / Arg2."}
|
||||||
|
, { 'MOD', 16#18, 3, false, 3, [a,a,a], modulo, "Arg0 := Arg1 mod Arg2."}
|
||||||
|
, { 'POW', 16#19, 3, false, 3, [a,a,a], pow, "Arg0 := Arg1 ^ Arg2."}
|
||||||
|
, { 'LT', 16#20, 3, false, 3, [a,a,a], lt, "Arg0 := Arg1 < Arg2."}
|
||||||
|
, { 'GT', 16#21, 3, false, 3, [a,a,a], gt, "Arg0 := Arg1 > Arg2."}
|
||||||
|
, { 'EQ', 16#22, 3, false, 3, [a,a,a], eq, "Arg0 := Arg1 = Arg2."}
|
||||||
|
, { 'ELT', 16#23, 3, false, 3, [a,a,a], elt, "Arg0 := Arg1 =< Arg2."}
|
||||||
|
, { 'EGT', 16#24, 3, false, 3, [a,a,a], egt, "Arg0 := Arg1 >= Arg2."}
|
||||||
|
, { 'NEQ', 16#25, 3, false, 3, [a,a,a], neq, "Arg0 := Arg1 /= Arg2."}
|
||||||
|
, { 'AND', 16#26, 3, false, 3, [a,a,a], and_op, "Arg0 := Arg1 and Arg2."}
|
||||||
|
, { 'OR', 16#27, 3, false, 3, [a,a,a], or_op, "Arg0 := Arg1 or Arg2."}
|
||||||
|
, { 'NOT', 16#28, 2, false, 3, [a,a], not_op, "Arg0 := not Arg1."}
|
||||||
|
, { 'TUPLE', 16#29, 1, false, 3, [ii], tuple, "Create a tuple of size = Arg0. Elements on stack."}
|
||||||
|
, { 'ELEMENT', 16#2a, 4, false, 3, [t,a,a,a], element_op, "Arg1 := element(Arg2, Arg3). The element should be of type Arg1"}
|
||||||
|
, { 'MAP_EMPTY', 16#2b, 1, false, 3, [a], map_empty, "Arg0 := #{}."}
|
||||||
|
, { 'MAP_LOOKUP', 16#2c, 3, false, 3, [a,a,a], map_lookup, "Arg0 := lookup key Arg2 in map Arg1."}
|
||||||
|
, { 'MAP_LOOKUPD', 16#2d, 4, false, 3, [a,a,a,a], map_lookup, "Arg0 := lookup key Arg2 in map Arg1 if key exists in map otherwise Arg0 := Arg3."}
|
||||||
|
, { 'MAP_UPDATE', 16#2e, 4, false, 3, [a,a,a,a], map_update, "Arg0 := update key Arg2 in map Arg1 with value Arg3."}
|
||||||
|
, { 'MAP_DELETE', 16#2f, 3, false, 3, [a,a,a], map_delete, "Arg0 := delete key Arg2 from map Arg1."}
|
||||||
|
, { 'MAP_MEMBER', 16#30, 3, false, 3, [a,a,a], map_member, "Arg0 := true if key Arg2 is in map Arg1."}
|
||||||
|
, { 'MAP_FROM_LIST',16#31, 2, false, 3, [a,a], map_from_list, "Arg0 := make a map from (key, value) list in Arg1."}
|
||||||
|
, { 'NIL', 16#32, 1, false, 3, [a], nil, "Arg0 := []."}
|
||||||
|
, { 'IS_NIL', 16#33, 2, false, 3, [a,a], is_nil, "Arg0 := true if Arg1 == []."}
|
||||||
|
, { 'CONS', 16#34, 3, false, 3, [a,a,a], cons, "Arg0 := [Arg1|Arg2]."}
|
||||||
|
, { 'HD', 16#35, 2, false, 3, [a,a], hd, "Arg0 := head of list Arg1."}
|
||||||
|
, { 'TL', 16#36, 2, false, 3, [a,a], tl, "Arg0 := tail of list Arg1."}
|
||||||
|
, { 'LENGTH', 16#37, 2, false, 3, [a,a], length, "Arg0 := length of list Arg1."}
|
||||||
|
, { 'STR_EQ', 16#38, 3, false, 3, [a,a,a], str_eq, "Arg0 := true iff the strings Arg1 and Arg2 are the same."}
|
||||||
|
, { 'STR_JOIN', 16#39, 3, false, 3, [a,a,a], str_join, "Arg0 := string Arg1 followed by string Arg2."}
|
||||||
|
, { 'INT_TO_STR', 16#40, 2, false, 3, [a,a], int_to_str, "Arg0 := turn integer Arg1 into a string."}
|
||||||
|
, { 'ADDR_TO_STR', 16#41, 2, false, 3, [a,a], addr_to_str, "Arg0 := turn address Arg1 into a string."}
|
||||||
|
, { 'STR_REVERSE', 16#42, 2, false, 3, [a,a], str_reverse, "Arg0 := the reverse of string Arg1."}
|
||||||
|
, { 'INT_TO_ADDR', 16#43, 2, false, 3, [a,a], int_to_addr, "Arg0 := turn integer Arg1 into an address."}
|
||||||
|
, { 'VARIANT', 16#44, 4, false, 3, [a,a,a,a], variant, "Arg0 := create a variant of size Arg1 with the tag Arg2 (Arg2 < Arg1) and take Arg3 elements from the stack."}
|
||||||
|
, { 'VARIANT_TEST', 16#45, 3, false, 3, [a,a,a], variant_test, "Arg0 := true if variant Arg1 has the tag Arg2."}
|
||||||
|
, { 'VARIANT_ELEMENT',16#46, 3, false, 3, [a,a,a], variant_element, "Arg0 := element number Arg2 from variant Arg1."}
|
||||||
|
, { 'BITS_NONEA', 16#47, 0, false, 3, atomic, bits_none, "accumulator := empty bitmap."}
|
||||||
|
, { 'BITS_NONE', 16#48, 1, false, 3, [a], bits_none, "Arg0 := empty bitmap."}
|
||||||
|
, { 'BITS_ALLA', 16#49, 0, false, 3, atomic, bits_all, "accumulator := full bitmap."}
|
||||||
|
, { 'BITS_ALL', 16#50, 1, false, 3, [a], bits_all, "Arg0 := full bitmap."}
|
||||||
|
, { 'BITS_ALL_N', 16#51, 2, false, 3, [a,a], bits_all_n, "Arg0 := bitmap with Arg1 bits set."}
|
||||||
|
, { 'BITS_SET', 16#52, 3, false, 3, [a,a,a], bits_set, "Arg0 := set bit Arg2 of bitmap Arg1."}
|
||||||
|
, { 'BITS_CLEAR', 16#53, 3, false, 3, [a,a,a], bits_clear, "Arg0 := clear bit Arg2 of bitmap Arg1."}
|
||||||
|
, { 'BITS_TEST', 16#54, 3, false, 3, [a,a,a], bits_test, "Arg0 := true if bit Arg2 of bitmap Arg1 is set."}
|
||||||
|
, { 'BITS_SUM', 16#55, 2, false, 3, [a,a], bits_sum, "Arg0 := sum of set bits in bitmap Arg1. Exception if infinit bitmap."}
|
||||||
|
, { 'BITS_OR', 16#56, 3, false, 3, [a,a,a], bits_or, "Arg0 := Arg1 v Arg2."}
|
||||||
|
, { 'BITS_AND', 16#57, 3, false, 3, [a,a,a], bits_and, "Arg0 := Arg1 ^ Arg2."}
|
||||||
|
, { 'BITS_DIFF', 16#58, 3, false, 3, [a,a,a], bits_diff, "Arg0 := Arg1 - Arg2."}
|
||||||
|
, { 'ADDRESS', 16#59, 1, false, 3, [a], address, "Arg0 := The current contract address."}
|
||||||
|
, { 'BALANCE', 16#5a, 1, false, 3, [a], balance, "Arg0 := The current contract address."}
|
||||||
|
, { 'ORIGIN', 16#5b, 1, false, 3, [a], origin, "Arg0 := Address of contract called by the call transaction."}
|
||||||
|
, { 'CALLER', 16#5c, 1, false, 3, [a], caller, "Arg0 := The address that signed the call transaction."}
|
||||||
|
, { 'GASPRICE', 16#5d, 1, false, 3, [a], gasprice, "Arg0 := The current gas price."}
|
||||||
|
, { 'BLOCKHASH', 16#5e, 1, false, 3, [a], blockhash, "Arg0 := The current blockhash."} %% TODO: Do we support has at height?
|
||||||
|
, { 'BENEFICIARY', 16#5f, 1, false, 3, [a], beneficiary, "Arg0 := The address of the current beneficiary."}
|
||||||
|
, { 'TIMESTAMP', 16#60, 1, false, 3, [a], timestamp, "Arg0 := The current timestamp. Unrelaiable, don't use for anything."}
|
||||||
|
, { 'GENERATION', 16#61, 1, false, 3, [a], generation, "Arg0 := The block height of the cureent generation."}
|
||||||
|
, { 'MICROBLOCK', 16#62, 1, false, 3, [a], microblock, "Arg0 := The current micro block number."}
|
||||||
|
, { 'DIFFICULTY', 16#63, 1, false, 3, [a], difficulty, "Arg0 := The current difficulty."}
|
||||||
|
, { 'GASLIMIT', 16#64, 1, false, 3, [a], gaslimit, "Arg0 := The current gaslimit."}
|
||||||
|
, { 'GAS', 16#65, 1, false, 3, [a], gas, "Arg0 := The amount of gas left."}
|
||||||
|
|
||||||
|
, { 'LOG0', 16#66, 2, false, 3, [a,a], log, "Create a log message in the call object."}
|
||||||
|
, { 'LOG1', 16#67, 3, false, 3, [a,a,a], log, "Create a log message with one topic in the call object."}
|
||||||
|
, { 'LOG2', 16#68, 4, false, 3, [a,a,a,a], log, "Create a log message with two topics in the call object."}
|
||||||
|
, { 'LOG3', 16#69, 5, false, 3, [a,a,a,a,a], log, "Create a log message with three topics in the call object."}
|
||||||
|
, { 'LOG4', 16#6a, 6, false, 3, [a,a,a,a,a,a], log, "Create a log message with four topics in the call object."}
|
||||||
|
, { 'DEACTIVATE', 16#6b, 0, false, 3, atomic, deactivate, "Mark the current contract for deactication."}
|
||||||
|
%% Transaction ops
|
||||||
|
, { 'SPEND', 16#6c, 2, false,3, [a,a], spend, "Transfer Arg0 tokens to account Arg1. (If the contract account has at least that many tokens."}
|
||||||
|
, { 'ORACLE_REGISTER', 16#6d, 6, false,3, [a,a,a,a,a,a], oracle_register, "Mark the current contract for deactication."}
|
||||||
|
%% TODO:
|
||||||
|
, { 'ORACLE_QUERY', 16#6e, 0, false,3, atomic, oracle_query, ""}
|
||||||
|
, { 'ORACLE_RESPOND', 16#6f, 0, false,3, atomic, oracle_respond, ""}
|
||||||
|
, { 'ORACLE_EXTEND', 16#70, 0, false,3, atomic, oracle_extend, ""}
|
||||||
|
, { 'ORACLE_GET_ANSWER', 16#71, 0, false,3, atomic, oracle_get_answer, ""}
|
||||||
|
, { 'ORACLE_GET_QUESTION', 16#72, 0, false,3, atomic,oracle_get_question, ""}
|
||||||
|
, { 'ORACLE_QUERY_FEE', 16#73, 0, false,3, atomic, oracle_query_fee, ""}
|
||||||
|
, { 'AENS_RESOLVE', 16#74, 0, false,3, atomic, aens_resolve, ""}
|
||||||
|
, { 'AENS_PRECLAIM', 16#75, 0, false,3, atomic, aens_preclaim, ""}
|
||||||
|
, { 'AENS_CLAIM', 16#76, 0, false,3, atomic, aens_claim, ""}
|
||||||
|
, { 'AENS_UPDATE', 16#77, 0, false,3, atomic, aend_update, ""}
|
||||||
|
, { 'AENS_TRANSFER', 16#78, 0, false,3, atomic, aens_transfer, ""}
|
||||||
|
, { 'AENS_REVOKE', 16#79, 0, false,3, atomic, aens_revoke, ""}
|
||||||
|
, { 'ECVERIFY', 16#7a, 0, false,3, atomic, ecverify, ""}
|
||||||
|
, { 'SHA3', 16#7b, 0, false,3, atomic, sha3, ""}
|
||||||
|
, { 'SHA256', 16#7c, 0, false,3, atomic, sha256, ""}
|
||||||
|
, { 'BLAKE2B', 16#7d, 0, false,3, atomic, blake2b, ""}
|
||||||
|
|
||||||
|
, {'ABORT', 16#fb, 1, false, 3, [a], abort, "Abort execution (dont use all gas) with error message in Arg0."}
|
||||||
|
, {'EXIT', 16#fc, 1, false, 3, [a], exit, "Abort execution (use upp all gas) with error message in Arg0."}
|
||||||
|
, { 'NOP', 16#fd, 0, false, 1, atomic, nop, "The no op. does nothing."}
|
||||||
|
%% FUNCTION 16#fe "Function declaration and entrypoint."
|
||||||
|
%% EXTEND 16#ff "Reserved for future extensions beyond one byte opcodes."
|
||||||
|
].
|
||||||
|
|
||||||
|
|
||||||
|
generate_header_file(Filename, Ops) ->
|
||||||
|
{ok, File} = file:open(Filename, [write]),
|
||||||
|
Defines = lists:flatten([gen_defines(Op) || Op <- Ops]),
|
||||||
|
io:format(File, "~s", [prelude("Provides opcode defines.\n")]),
|
||||||
|
io:format(File, "%% FATE opcodes\n~s", [Defines]),
|
||||||
|
io:format(File, "~s",
|
||||||
|
["-define('FUNCTION' , 16#fe).\n"
|
||||||
|
"-define('EXTEND' , 16#ff).\n\n"]),
|
||||||
|
file:close(File).
|
||||||
|
|
||||||
|
generate_opcodes_ops(Modulename, HrlFile, SrcDir, Ops) ->
|
||||||
|
Filename = SrcDir ++ atom_to_list(Modulename) ++ ".erl",
|
||||||
|
|
||||||
|
{ok, File} = file:open(Filename, [write]),
|
||||||
|
Mnemonic = lists:flatten([gen_mnemonic(Op) || Op <- Ops]),
|
||||||
|
ToOp = lists:flatten([gen_m_to_op(Op) || Op <- Ops]),
|
||||||
|
Args = lists:flatten([gen_args(Op) || Op <- Ops]),
|
||||||
|
EndBB = lists:flatten([gen_bb(Op) || Op <- Ops]),
|
||||||
|
|
||||||
|
io:format(File, "~s", [prelude("Provides opcode primitives.\n")]),
|
||||||
|
io:format(File, "~s", [ops_exports(Modulename, HrlFile,
|
||||||
|
["args/1\n"
|
||||||
|
" , end_bb/1\n"
|
||||||
|
" , mnemonic/1\n"
|
||||||
|
" , m_to_op/1\n"
|
||||||
|
])]),
|
||||||
|
|
||||||
|
io:format(File, "%% FATE mnemonics\n~s", [Mnemonic]),
|
||||||
|
io:format(File, "mnemonic(Op) -> exit({bad_opcode, Op}).\n\n", []),
|
||||||
|
|
||||||
|
io:format(File, "%% FATE opcodes\n~s", [ToOp]),
|
||||||
|
io:format(File, "m_to_op(M) -> exit({bad_mnemonic, M}).\n\n", []),
|
||||||
|
|
||||||
|
io:format(File, "%% FATE numbers of args to op.\n~s", [Args]),
|
||||||
|
io:format(File, "args(Op) -> exit({bad_opcode, Op}).\n\n", []),
|
||||||
|
|
||||||
|
io:format(File, "%% Does FATE Op end a Basic Block?\n~s", [EndBB]),
|
||||||
|
io:format(File, "end_bb(_) -> false.\n\n", []),
|
||||||
|
|
||||||
|
file:close(File).
|
||||||
|
|
||||||
|
generate_code_ops(Modulename, SrcDir, Ops) ->
|
||||||
|
Filename = SrcDir ++ atom_to_list(Modulename) ++ ".erl",
|
||||||
|
|
||||||
|
{ok, File} = file:open(Filename, [write]),
|
||||||
|
Types = lists:flatten([gen_type(Op) || Op <- Ops]),
|
||||||
|
TypeExports = lists:flatten([gen_type_exports(Op) || Op <- Ops]),
|
||||||
|
[#{type_name := FirstType} | RestOfOps] = Ops,
|
||||||
|
FateTypes = lists:flatten([gen_fate_code_type(Op) || Op <- RestOfOps]),
|
||||||
|
ConstructorExports = lists:flatten([gen_constructor_exports(Op) || Op <- Ops]),
|
||||||
|
Constructors = lists:flatten([gen_constructors(Op) || Op <- Ops]),
|
||||||
|
|
||||||
|
io:format(File, "~s", [prelude(" Provide constructor functuions for "
|
||||||
|
"Fate instructions.\n%%% Provide types"
|
||||||
|
" and documentation for Fate "
|
||||||
|
"instructions.\n")]),
|
||||||
|
io:format(File, "-module(~w).\n\n", [Modulename]),
|
||||||
|
io:format(File, "-include_lib(\"aebytecode/include/aeb_fate_data.hrl\").\n\n"
|
||||||
|
"-define(i(__X__), {immediate, __X__ }).\n\n"
|
||||||
|
"-type fate_arg_immediate(T) :: {immediate, T}.\n"
|
||||||
|
"-type fate_arg_var() :: {var, integer()}.\n"
|
||||||
|
"-type fate_arg_arg() :: {arg, integer()}.\n"
|
||||||
|
"-type fate_arg_stack() :: {stack, integer()}.\n"
|
||||||
|
"-type fate_arg() :: fate_arg_immediate()\n"
|
||||||
|
" | fate_arg_var()\n"
|
||||||
|
" | fate_arg_arg()\n"
|
||||||
|
" | fate_arg_stack().\n\n"
|
||||||
|
"-type fate_arg_immediate() :: {immediate, aeb_fate_data:fate_type()}.\n"
|
||||||
|
, []),
|
||||||
|
io:format(File, "~s", [Types]),
|
||||||
|
io:format(File, "-type fate_code() :: ~s\n~s .\n\n",
|
||||||
|
[FirstType, FateTypes]),
|
||||||
|
io:format(File, "-export_type([ fate_code/0\n~s ]).\n\n", [TypeExports]),
|
||||||
|
io:format(File, "-export([ foo/0\n~s ]).\n\n", [ConstructorExports]),
|
||||||
|
io:format(File, "~s\n", [Constructors]),
|
||||||
|
|
||||||
|
io:format(File, "foo() -> \"A temp hack.\".\n", []),
|
||||||
|
|
||||||
|
file:close(File).
|
||||||
|
|
||||||
|
gen_type(#{type_name := TypeName, type := Type}) ->
|
||||||
|
lists:flatten(io_lib:format("-type ~-26s :: ~s.\n",
|
||||||
|
[TypeName, Type])).
|
||||||
|
|
||||||
|
gen_fate_code_type(#{type_name := TypeName}) ->
|
||||||
|
lists:flatten(io_lib:format(" | ~s\n", [TypeName])).
|
||||||
|
|
||||||
|
gen_type_exports(#{type_name := TypeName}) ->
|
||||||
|
lists:flatten(io_lib:format(" , ~s/0\n", [TypeName--"()"])).
|
||||||
|
|
||||||
|
gen_constructor_exports(#{constructor_type := Function}) ->
|
||||||
|
lists:flatten(io_lib:format(" , ~s\n", [Function])).
|
||||||
|
|
||||||
|
gen_constructors(#{constructor := Function, format := atomic,
|
||||||
|
type_name := Type, opname := Name}) ->
|
||||||
|
lists:flatten(io_lib:format("-spec ~s() -> ~s.\n"
|
||||||
|
"~s() ->\n"
|
||||||
|
" ~w.\n\n",
|
||||||
|
[Function, Type, Function, Name]));
|
||||||
|
gen_constructors(#{constructor := Function, format := ArgSpec,
|
||||||
|
type_name := Type, opname := Name}) ->
|
||||||
|
ArgTypeSpecs = gen_arg_type_specs(ArgSpec),
|
||||||
|
Args = gen_arg_names(0, ArgSpec),
|
||||||
|
UseArgs = gen_arg_uses(0, ArgSpec),
|
||||||
|
lists:flatten(io_lib:format("-spec ~s(~s) -> ~s.\n"
|
||||||
|
"~s(~s) ->\n"
|
||||||
|
" {~w, ~s}.\n\n",
|
||||||
|
[Function, ArgTypeSpecs, Type,
|
||||||
|
Function, Args, Name, UseArgs])).
|
||||||
|
|
||||||
|
gen_arg_type_specs([]) -> [];
|
||||||
|
gen_arg_type_specs([a]) -> "fate_arg()";
|
||||||
|
gen_arg_type_specs([is]) -> "aeb_fate_data:fate_string()";
|
||||||
|
gen_arg_type_specs([ii]) -> "aeb_fate_data:fate_integer()";
|
||||||
|
gen_arg_type_specs([li]) -> "[aeb_fate_data:fate_integer()]";
|
||||||
|
gen_arg_type_specs([t]) -> "aeb_fate_data:fate_type_type()";
|
||||||
|
gen_arg_type_specs([a | Args]) -> "fate_arg(), " ++ gen_arg_type_specs(Args);
|
||||||
|
gen_arg_type_specs([is | Args]) -> "aeb_fate_data:fate_string(), " ++ gen_arg_type_specs(Args);
|
||||||
|
gen_arg_type_specs([ii | Args]) -> "aeb_fate_data:fate_integer(), " ++ gen_arg_type_specs(Args);
|
||||||
|
gen_arg_type_specs([li | Args]) -> "[aeb_fate_data:fate_integer()], " ++ gen_arg_type_specs(Args);
|
||||||
|
gen_arg_type_specs([t | Args]) -> "aeb_fate_data:fate_type_type(), " ++ gen_arg_type_specs(Args).
|
||||||
|
|
||||||
|
|
||||||
|
gen_arg_names(_, []) ->
|
||||||
|
[];
|
||||||
|
gen_arg_names(N, [_]) -> io_lib:format("Arg~w", [N]);
|
||||||
|
gen_arg_names(N, [_|Args]) ->
|
||||||
|
io_lib:format("Arg~w, ", [N]) ++ gen_arg_names(N+1, Args).
|
||||||
|
|
||||||
|
gen_arg_uses(_, []) ->
|
||||||
|
[];
|
||||||
|
gen_arg_uses(N, [a]) -> io_lib:format("Arg~w", [N]);
|
||||||
|
gen_arg_uses(N, [is]) -> io_lib:format("{immediate, Arg~w}", [N]);
|
||||||
|
gen_arg_uses(N, [ii]) -> io_lib:format("{immediate, Arg~w}", [N]);
|
||||||
|
gen_arg_uses(N, [li]) -> io_lib:format("[{immediate, I} || I <- Arg~w]", [N]);
|
||||||
|
gen_arg_uses(N, [t]) -> io_lib:format("Arg~w", [N]);
|
||||||
|
gen_arg_uses(N, [a | Args]) ->
|
||||||
|
io_lib:format("Arg~w, ", [N]) ++ gen_arg_uses(N+1, Args);
|
||||||
|
gen_arg_uses(N, [is | Args]) ->
|
||||||
|
io_lib:format("{immediate, Arg~w}, ", [N]) ++ gen_arg_uses(N+1, Args);
|
||||||
|
gen_arg_uses(N, [ii | Args]) ->
|
||||||
|
io_lib:format("{immediate, Arg~w}, ", [N]) ++ gen_arg_uses(N+1, Args);
|
||||||
|
gen_arg_uses(N, [li | Args]) ->
|
||||||
|
io_lib:format("[{immediate, I} || I <- Arg~w], ", [N]) ++ gen_arg_uses(N+1, Args);
|
||||||
|
gen_arg_uses(N, [t | Args]) ->
|
||||||
|
io_lib:format("Arg~w, ", [N]) ++ gen_arg_uses(N+1, Args).
|
||||||
|
|
||||||
|
|
||||||
|
ops_exports(Module, HrlFile, Exports) ->
|
||||||
|
lists:flatten(io_lib:format(
|
||||||
|
"-module(~w).\n\n"
|
||||||
|
"-export([ ~s ]).\n\n"
|
||||||
|
"-include_lib(\"aebytecode/" ++ HrlFile ++"\").\n\n"
|
||||||
|
"%%====================================================================\n"
|
||||||
|
"%% API\n"
|
||||||
|
"%%====================================================================\n",
|
||||||
|
[Module, Exports])).
|
||||||
|
|
||||||
|
gen_mnemonic(#{opname := Name, macro := Macro}) ->
|
||||||
|
lists:flatten(io_lib:format("mnemonic(~21s) -> ~21w ;\n",
|
||||||
|
[Macro, Name])).
|
||||||
|
|
||||||
|
|
||||||
|
gen_m_to_op(#{opname := Name, macro := Macro}) ->
|
||||||
|
lists:flatten(io_lib:format("m_to_op(~21w) -> ~21s ;\n",
|
||||||
|
[Name, Macro])).
|
||||||
|
|
||||||
|
gen_args(#{macro := Macro, args := Args}) ->
|
||||||
|
lists:flatten(io_lib:format("args(~21s) -> ~2w ;\n",
|
||||||
|
[Macro, Args])).
|
||||||
|
|
||||||
|
gen_bb(#{macro := Macro, end_bb := EndBB}) ->
|
||||||
|
lists:flatten(io_lib:format("end_bb(~21s) -> ~w ;\n",
|
||||||
|
[Macro, EndBB])).
|
||||||
|
|
||||||
|
|
||||||
|
prelude(Doc) ->
|
||||||
|
"%%%-------------------------------------------------------------------\n"
|
||||||
|
"%%% @copyright (C) 2019, Aeternity Anstalt\n"
|
||||||
|
"%%%\n"
|
||||||
|
"%%% === === N O T E : This file is generated do not edit. === ===\n"
|
||||||
|
"%%%\n"
|
||||||
|
"%%% Source is in aeb_fate_generate_ops.erl\n"
|
||||||
|
"%%% @doc\n"
|
||||||
|
"%%% "++Doc++
|
||||||
|
"%%% @end\n"
|
||||||
|
"%%%-------------------------------------------------------------------\n\n".
|
||||||
|
|
||||||
|
|
||||||
|
gen_defines(#{opname := Name, opcode := OpCode}) ->
|
||||||
|
lists:flatten(io_lib:format("-define(~-26w, 16#~2.16.0b).\n", [Name, OpCode])).
|
||||||
|
|
||||||
|
gen([]) ->
|
||||||
|
[];
|
||||||
|
gen([{OpName, OpCode, Args, EndBB, Gas, FateFormat, Constructor, Doc} | Rest]) ->
|
||||||
|
Name = atom_to_list(OpName),
|
||||||
|
LowerName = string:to_lower(Name),
|
||||||
|
TypeName = "fate_" ++ LowerName ++ "()",
|
||||||
|
Macro = "?" ++ Name,
|
||||||
|
Type = case FateFormat of
|
||||||
|
atomic -> io_lib:format("~w", [OpName]);
|
||||||
|
ArgTypes ->
|
||||||
|
io_lib:format("{~w, ~s}", [OpName, expand_types(ArgTypes)])
|
||||||
|
end,
|
||||||
|
ConstructorType = atom_to_list(Constructor) ++ "/" ++ io_lib:format("~w", [Args]),
|
||||||
|
|
||||||
|
[#{ opname => OpName
|
||||||
|
, opcode => OpCode
|
||||||
|
, args => Args
|
||||||
|
, end_bb => EndBB
|
||||||
|
, format => FateFormat
|
||||||
|
, macro => Macro
|
||||||
|
, type_name => TypeName
|
||||||
|
, doc => Doc
|
||||||
|
, gas => Gas
|
||||||
|
, type => Type
|
||||||
|
, constructor => Constructor
|
||||||
|
, constructor_type => ConstructorType
|
||||||
|
}| gen(Rest)].
|
||||||
|
|
||||||
|
|
||||||
|
expand_types([]) -> "";
|
||||||
|
expand_types([T]) -> expand_type(T);
|
||||||
|
expand_types([T|Ts]) ->expand_type(T) ++ ", " ++ expand_types(Ts).
|
||||||
|
|
||||||
|
expand_type(a) -> "fate_arg()";
|
||||||
|
expand_type(is) -> "fate_arg_immediate(aeb_fate_data:fate_string())";
|
||||||
|
expand_type(ii) -> "fate_arg_immediate(aeb_fate_data:fate_integer())";
|
||||||
|
expand_type(li) -> "[fate_arg_immediate(aeb_fate_data:fate_integer())]";
|
||||||
|
expand_type(t) -> "aeb_fate_data:fate_type_type()".
|
||||||
|
|
||||||
|
generate_scanner(TemplateFile, Outfile, Path, Ops) ->
|
||||||
|
{ok, Template} = file:read_file(filename:join(Path,TemplateFile)),
|
||||||
|
Tokens = lists:flatten([gen_token(Op) || Op <- Ops]),
|
||||||
|
NewFile = insert_tokens_in_template(Template, Tokens),
|
||||||
|
file:write_file(filename:join(Path, Outfile), NewFile).
|
||||||
|
|
||||||
|
gen_token(#{opname := OpName}) ->
|
||||||
|
Name = atom_to_list(OpName),
|
||||||
|
io_lib:format("~-28s: {token, {mnemonic, TokenLine, ~w}}.\n",
|
||||||
|
[Name, OpName]).
|
||||||
|
|
||||||
|
insert_tokens_in_template(<<"###REPLACEWITHOPTOKENS###", Rest/binary >>, Tokens) ->
|
||||||
|
[Tokens, Rest];
|
||||||
|
insert_tokens_in_template(<<"###REPLACEWITHNOTE###", Rest/binary >>, Tokens) ->
|
||||||
|
[
|
||||||
|
"%%%\n"
|
||||||
|
"%%% === === N O T E : This file is generated do not edit. === ===\n"
|
||||||
|
"%%%\n"
|
||||||
|
"%%% Source is in aeb_fate_generate_ops.erl\n"
|
||||||
|
"%%% and aeb_fate_asm_scan.template"
|
||||||
|
| insert_tokens_in_template(Rest, Tokens)];
|
||||||
|
insert_tokens_in_template(<<B,Rest/binary>>, Tokens) ->
|
||||||
|
[B|insert_tokens_in_template(Rest, Tokens)].
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
{application, aebytecode,
|
{application, aebytecode,
|
||||||
[{description, "Bytecode definitions for AEthernity VM shared with compiler."},
|
[{description, "Bytecode definitions, serialization and deserialization for aeternity."},
|
||||||
{vsn, "1.0.0"},
|
{vsn, "2.0.1"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications,
|
{applications,
|
||||||
[kernel,
|
[kernel,
|
||||||
stdlib
|
stdlib,
|
||||||
|
eblake2,
|
||||||
|
aeserialization,
|
||||||
|
getopt
|
||||||
]},
|
]},
|
||||||
{env,[]},
|
{env,[]},
|
||||||
{modules, []},
|
{modules, []},
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
-module(aefateasm).
|
||||||
|
|
||||||
|
-export([main/1]).
|
||||||
|
|
||||||
|
-define(OPT_SPEC,
|
||||||
|
[ {src_file, undefined, undefined, string, "Fate assembler code file"}
|
||||||
|
, {verbose, $v, "verbose", undefined, "Verbose output"}
|
||||||
|
, {help, $h, "help", undefined, "Show this message"}
|
||||||
|
, {outfile, $o, "out", string, "Output file (experimental)"} ]).
|
||||||
|
|
||||||
|
usage() ->
|
||||||
|
getopt:usage(?OPT_SPEC, "aefateasm").
|
||||||
|
|
||||||
|
main(Args) ->
|
||||||
|
case getopt:parse(?OPT_SPEC, Args) of
|
||||||
|
{ok, {Opts, []}} ->
|
||||||
|
case proplists:get_value(help, Opts, false) of
|
||||||
|
false ->
|
||||||
|
assemble(Opts);
|
||||||
|
true ->
|
||||||
|
usage()
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ok, {_, NonOpts}} ->
|
||||||
|
io:format("Can't understand ~p\n\n", [NonOpts]),
|
||||||
|
usage();
|
||||||
|
|
||||||
|
{error, {Reason, Data}} ->
|
||||||
|
io:format("Error: ~s ~p\n\n", [Reason, Data]),
|
||||||
|
usage()
|
||||||
|
end.
|
||||||
|
|
||||||
|
assemble(Opts) ->
|
||||||
|
case proplists:get_value(src_file, Opts, undefined) of
|
||||||
|
undefined ->
|
||||||
|
io:format("Error: no input source file\n\n"),
|
||||||
|
usage();
|
||||||
|
File ->
|
||||||
|
assemble(File, Opts)
|
||||||
|
end.
|
||||||
|
|
||||||
|
assemble(File, Opts) ->
|
||||||
|
Verbose = proplists:get_value(verbose, Opts, false),
|
||||||
|
case proplists:get_value(outfile, Opts, undefined) of
|
||||||
|
undefined ->
|
||||||
|
Asm = aeb_fate_asm:read_file(File),
|
||||||
|
{Env, BC} = aeb_fate_asm:asm_to_bytecode(Asm, Opts),
|
||||||
|
case Verbose of
|
||||||
|
true ->
|
||||||
|
io:format("Env: ~0p~n", [Env]);
|
||||||
|
false -> ok
|
||||||
|
end,
|
||||||
|
io:format("Code: ~0p~n", [BC]);
|
||||||
|
OutFile ->
|
||||||
|
aeb_fate_asm:assemble_file(File, OutFile, Opts)
|
||||||
|
end.
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||||
|
%%% @doc Basic tests for Fate data
|
||||||
|
%%% @end
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(aeb_data_test).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
format_integer_test() ->
|
||||||
|
"0" = aeb_fate_data:format(0).
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||||
|
%%% @doc Basic tests for Fate serialization
|
||||||
|
%%%
|
||||||
|
%%% To run:
|
||||||
|
%%% TEST=aeb_fate_asm_test rebar3 eunit
|
||||||
|
%%%
|
||||||
|
%%% @end
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(aeb_fate_asm_test).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
asm_path() ->
|
||||||
|
filename:join(code:lib_dir(aebytecode, test), "asm_code").
|
||||||
|
|
||||||
|
|
||||||
|
file_path(File) ->
|
||||||
|
filename:join(asm_path(), File) ++ ".fate".
|
||||||
|
|
||||||
|
read_file(File) ->
|
||||||
|
FilePath = file_path(File),
|
||||||
|
Asm = aeb_fate_asm:read_file(FilePath),
|
||||||
|
Asm.
|
||||||
|
|
||||||
|
assemble(Asm) ->
|
||||||
|
{Env, BC} = aeb_fate_asm:asm_to_bytecode(Asm, []),
|
||||||
|
{Env, BC}.
|
||||||
|
|
||||||
|
disassemble(BC) ->
|
||||||
|
aeb_fate_asm:bytecode_to_fate_code(BC, []).
|
||||||
|
|
||||||
|
|
||||||
|
asm_disasm_idenity_test() ->
|
||||||
|
check_roundtrip(identity).
|
||||||
|
|
||||||
|
asm_disasm_files_test_() ->
|
||||||
|
[{lists:flatten(io_lib:format("~p", [X])),
|
||||||
|
fun() -> check_roundtrip(X) end}
|
||||||
|
|| X <- sources()].
|
||||||
|
|
||||||
|
sources() ->
|
||||||
|
[ "arith"
|
||||||
|
, "bool"
|
||||||
|
, "comp"
|
||||||
|
, "jumpif"
|
||||||
|
, "map"
|
||||||
|
, "memory"
|
||||||
|
, "remote"
|
||||||
|
, "test"
|
||||||
|
, "tuple"
|
||||||
|
].
|
||||||
|
|
||||||
|
check_roundtrip(File) ->
|
||||||
|
AssemblerCode = read_file(File),
|
||||||
|
{_Env, ByteCode} = assemble(AssemblerCode),
|
||||||
|
FateCode = disassemble(ByteCode),
|
||||||
|
DissasmCode = aeb_fate_asm:to_asm(FateCode),
|
||||||
|
io:format("~s~n", [AssemblerCode]),
|
||||||
|
io:format("~s~n", [DissasmCode]),
|
||||||
|
{_Env2, ByteCode2} = assemble(DissasmCode),
|
||||||
|
Code1 = aeb_fate_asm:strip(ByteCode),
|
||||||
|
Code2 = aeb_fate_asm:strip(ByteCode2),
|
||||||
|
?assertEqual(Code1, Code2).
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||||
|
%%% @doc Basic tests for Fate serialization
|
||||||
|
%%%
|
||||||
|
%%% To run:
|
||||||
|
%%% TEST=aeb_serialize_test rebar3 eunit
|
||||||
|
%%%
|
||||||
|
%%% @end
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(aeb_serialize_test).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
serialize_integer_test() ->
|
||||||
|
<<0>> = aeb_fate_encoding:serialize(aeb_fate_data:make_integer(0)),
|
||||||
|
<<2>> = aeb_fate_encoding:serialize(aeb_fate_data:make_integer(1)),
|
||||||
|
<<126>> = aeb_fate_encoding:serialize(aeb_fate_data:make_integer(63)),
|
||||||
|
<<111, 0>> = aeb_fate_encoding:serialize(aeb_fate_data:make_integer(64)),
|
||||||
|
<<111,130,255,255>> = aeb_fate_encoding:serialize(aeb_fate_data:make_integer(65535 + 64)),
|
||||||
|
<<111,184,129,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>> =
|
||||||
|
aeb_fate_encoding:serialize(aeb_fate_data:make_integer(1 bsl 1024 + 64)).
|
||||||
|
|
||||||
|
serialize_deserialize_test_() ->
|
||||||
|
[{lists:flatten(io_lib:format("~p", [X])),
|
||||||
|
fun() ->
|
||||||
|
?assertEqual(X,
|
||||||
|
aeb_fate_encoding:deserialize(aeb_fate_encoding:serialize(X)))
|
||||||
|
end}
|
||||||
|
|| X <- sources()].
|
||||||
|
|
||||||
|
make_int_list(N) -> [aeb_fate_data:make_integer(I) || I <- lists:seq(1, N)].
|
||||||
|
|
||||||
|
sources() ->
|
||||||
|
FortyTwo = aeb_fate_data:make_integer(42),
|
||||||
|
Unit = aeb_fate_data:make_unit(),
|
||||||
|
True = aeb_fate_data:make_boolean(true),
|
||||||
|
False = aeb_fate_data:make_boolean(false),
|
||||||
|
Nil = aeb_fate_data:make_list([]),
|
||||||
|
EmptyString = aeb_fate_data:make_string(""),
|
||||||
|
EmptyMap = aeb_fate_data:make_map(#{}),
|
||||||
|
[aeb_fate_data:make_integer(0),
|
||||||
|
aeb_fate_data:make_integer(1),
|
||||||
|
True, False, Unit, Nil, EmptyString, EmptyMap,
|
||||||
|
aeb_fate_data:make_list([True]),
|
||||||
|
aeb_fate_data:make_address(
|
||||||
|
<<0,1,2,3,4,5,6,7,8,9,
|
||||||
|
0,1,2,3,4,5,6,7,8,9,
|
||||||
|
0,1,2,3,4,5,6,7,8,9,
|
||||||
|
1,2>>),
|
||||||
|
aeb_fate_data:make_string(<<"Hello">>),
|
||||||
|
aeb_fate_data:make_string(
|
||||||
|
<<"0123456789012345678901234567890123456789"
|
||||||
|
"0123456789012345678901234567890123456789"
|
||||||
|
"0123456789012345678901234567890123456789"
|
||||||
|
"0123456789012345678901234567890123456789">>), %% Magic concat 80 char string.
|
||||||
|
aeb_fate_data:make_tuple({True, FortyTwo}),
|
||||||
|
aeb_fate_data:make_tuple(list_to_tuple(make_int_list(65))),
|
||||||
|
aeb_fate_data:make_map(#{ aeb_fate_data:make_integer(1) => True, aeb_fate_data:make_integer(2) => False}),
|
||||||
|
aeb_fate_data:make_map(#{ aeb_fate_data:make_string(<<"foo">>) => aeb_fate_data:make_tuple({FortyTwo, True})}),
|
||||||
|
aeb_fate_data:make_list(make_int_list(3)),
|
||||||
|
aeb_fate_data:make_integer(-65),
|
||||||
|
aeb_fate_data:make_integer(65),
|
||||||
|
aeb_fate_data:make_integer(-32432847932847928374983),
|
||||||
|
aeb_fate_data:make_bits(0),
|
||||||
|
aeb_fate_data:make_bits(1),
|
||||||
|
aeb_fate_data:make_bits(-1),
|
||||||
|
aeb_fate_data:make_list(make_int_list(65)),
|
||||||
|
aeb_fate_data:make_variant(2, 0, {FortyTwo}),
|
||||||
|
aeb_fate_data:make_variant(2, 1, {}),
|
||||||
|
aeb_fate_data:make_list([aeb_fate_data:make_variant(3, 0, {})]),
|
||||||
|
aeb_fate_data:make_variant(255, 254, {}),
|
||||||
|
aeb_fate_data:make_variant(5, 3, {aeb_fate_data:make_boolean(true),
|
||||||
|
aeb_fate_data:make_list(make_int_list(3)),
|
||||||
|
aeb_fate_data:make_string(<<"foo">>)})
|
||||||
|
|
||||||
|
].
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
;; CONTRACT arith
|
||||||
|
|
||||||
|
FUNCTION add (integer, integer) : integer
|
||||||
|
ADD a arg0 arg1
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION sub (integer, integer) : integer
|
||||||
|
SUB a arg0 arg1
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION mul (integer, integer) : integer
|
||||||
|
MUL a arg0 arg1
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION div (integer, integer) : integer
|
||||||
|
DIV a arg0 arg1
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION mod (integer, integer) : integer
|
||||||
|
MOD a arg0 arg1
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION pow (integer, integer) : integer
|
||||||
|
POW a arg0 arg1
|
||||||
|
RETURN
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
;; CONTRACT bool
|
||||||
|
|
||||||
|
FUNCTION and(boolean, boolean) : boolean
|
||||||
|
AND a arg0 arg1
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION or(boolean, boolean) : boolean
|
||||||
|
OR a arg0 arg1
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION not(boolean) : boolean
|
||||||
|
NOT a arg0
|
||||||
|
RETURN
|
||||||
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
;; CONTRACT comp
|
||||||
|
|
||||||
|
FUNCTION lt(integer, integer) : boolean
|
||||||
|
LT a arg0 arg1
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION gt(integer, integer) : boolean
|
||||||
|
GT a arg0 arg1
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION egt(integer, integer) : boolean
|
||||||
|
EGT a arg0 arg1
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION elt(integer, integer) : boolean
|
||||||
|
ELT a arg0 arg1
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION eq(integer, integer) : boolean
|
||||||
|
EQ a arg0 arg1
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION neq(integer, integer) : boolean
|
||||||
|
NEQ a arg0 arg1
|
||||||
|
RETURN
|
||||||
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
;; CONTRACT: Identity
|
||||||
|
FUNCTION id(integer) -> integer
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
;; Test the code from the shell
|
||||||
|
;; _build/default/rel/aessembler/bin/aessembler console
|
||||||
|
|
||||||
|
;; aeb_aefa:file("../../../../test/asm_code/identity.fate", []).
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
;; CONTRACT jumpif
|
||||||
|
FUNCTION skip(integer, integer) : integer
|
||||||
|
;; BB : 0
|
||||||
|
PUSH arg1
|
||||||
|
PUSH 0
|
||||||
|
EQ a a arg0
|
||||||
|
JUMPIF a 2
|
||||||
|
;; BB : 1
|
||||||
|
INCA
|
||||||
|
JUMP 2
|
||||||
|
RETURN
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
;; CONTRACT map
|
||||||
|
FUNCTION make_empty_map():{map, integer, boolean}
|
||||||
|
MAP_EMPTY a
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION map_update({map, integer, boolean}, integer, boolean):{map, integer, boolean}
|
||||||
|
MAP_UPDATE a arg0 arg1 arg2
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION map_lookup({map, integer, boolean}, integer):boolean
|
||||||
|
MAP_LOOKUP a arg0 arg1
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION map_lookup_default({map, integer, boolean}, integer): boolean
|
||||||
|
MAP_LOOKUPD a arg0 arg1 false
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION map_member({map, integer, boolean}, integer):boolean
|
||||||
|
MAP_MEMBER a arg0 arg1
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION map_delete({map, integer, boolean}, integer):{map, integer, boolean}
|
||||||
|
MAP_DELETE a arg0 arg1
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION map_member({map, integer, boolean}, integer) : boolean
|
||||||
|
MAP_MEMBER a arg0 arg1
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION map_from_list({list, {tuple, [integer, boolean]}}) : {map, integer, boolean}
|
||||||
|
MAP_FROM_LIST a arg0
|
||||||
|
RETURN
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
;; CONTRACT memory
|
||||||
|
FUNCTION call(integer):integer
|
||||||
|
STORE var1 arg0
|
||||||
|
PUSH 0
|
||||||
|
CALL write
|
||||||
|
PUSH var1
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION write(integer):integer
|
||||||
|
STORE var1 arg0
|
||||||
|
RETURNR var1
|
||||||
|
|
||||||
|
FUNCTION dest_add(integer, integer): integer
|
||||||
|
STORE var1 arg0
|
||||||
|
STORE var2 arg1
|
||||||
|
ADD var3 var1 var2
|
||||||
|
PUSH var3
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION dest_add_imm(integer):integer
|
||||||
|
STORE var1 arg0
|
||||||
|
ADD var3 var1 2
|
||||||
|
PUSH var3
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION dest_add_stack(integer, integer): integer
|
||||||
|
STORE var1 arg0
|
||||||
|
PUSH arg1
|
||||||
|
ADD var3 var1 a
|
||||||
|
PUSH var3
|
||||||
|
RETURN
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
;; CONTRACT remote
|
||||||
|
FUNCTION add_five(integer):integer
|
||||||
|
ADD a 5 arg0
|
||||||
|
RETURN
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
;; CONTRACT: Test
|
||||||
|
FUNCTION id(integer) -> integer
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION jumps() -> integer
|
||||||
|
PUSH 0
|
||||||
|
JUMP 3
|
||||||
|
NOP
|
||||||
|
JUMP 2
|
||||||
|
NOP
|
||||||
|
RETURN
|
||||||
|
NOP
|
||||||
|
JUMP 1
|
||||||
|
|
||||||
|
FUNCTION inc(integer) -> integer
|
||||||
|
INCA
|
||||||
|
INCA
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION call(integer) -> integer
|
||||||
|
INCA
|
||||||
|
CALL inc
|
||||||
|
INCA
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
|
||||||
|
FUNCTION tailcall(integer) -> integer
|
||||||
|
INCA
|
||||||
|
CALL_T inc
|
||||||
|
|
||||||
|
FUNCTION remote_call(integer) : integer
|
||||||
|
PUSH arg0
|
||||||
|
CALL_R remote.add_five
|
||||||
|
INCA
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION remote_tailcall(integer) : integer
|
||||||
|
PUSH arg0
|
||||||
|
CALL_TR remote add_five
|
||||||
|
|
||||||
|
;; Test the code from the shell
|
||||||
|
;; _build/default/rel/aessembler/bin/aessembler console
|
||||||
|
|
||||||
|
;; aeb_aefa:file("../../../../test/asm_code/test.fate", []).
|
||||||
|
;; f(Asm), f(Env), f(BC), Asm = aefa_asm:read_file("../../../../test/asm_code/test.fate"), {Env, BC} = aefa_asm:asm_to_bytecode(Asm, []), aefa_asm:bytecode_to_fate_code(BC, []).
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
FUNCTION make_0tuple():{tuple, []}
|
||||||
|
;; BB : 0
|
||||||
|
TUPLE 0
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION make_2tuple(integer, integer):{tuple, [integer, integer]}
|
||||||
|
;; BB : 0
|
||||||
|
PUSH arg0
|
||||||
|
PUSH arg1
|
||||||
|
TUPLE 2
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION make_5tuple(integer, integer, integer, integer, integer):
|
||||||
|
{tuple, [integer, integer, integer, integer, integer]}
|
||||||
|
;; BB : 0
|
||||||
|
PUSH arg0
|
||||||
|
PUSH arg1
|
||||||
|
PUSH arg2
|
||||||
|
PUSH arg3
|
||||||
|
PUSH arg4
|
||||||
|
TUPLE 5
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION element1(integer, integer): integer
|
||||||
|
;; BB : 0
|
||||||
|
PUSH arg0
|
||||||
|
PUSH arg1
|
||||||
|
TUPLE 2
|
||||||
|
ELEMENT integer a 1 a
|
||||||
|
RETURN
|
||||||
|
|
||||||
|
FUNCTION element({tuple, [integer, integer]}, integer): integer
|
||||||
|
;; BB : 0
|
||||||
|
ELEMENT integer a arg1 arg0
|
||||||
|
RETURN
|
||||||
Reference in New Issue
Block a user