diff --git a/src/hz_aaci.erl b/src/hz_aaci.erl index 8fda712..dfd158c 100644 --- a/src/hz_aaci.erl +++ b/src/hz_aaci.erl @@ -23,7 +23,7 @@ erlang_args_to_fate/2, get_function_signature/2]). % Internal stuff that is useful for writing AACI unit tests. --export([annotate_type/2]). +-export([aaci_from_string/1, annotate_type/2]). %%% Types diff --git a/src/hz_sophia.erl b/src/hz_sophia.erl index b428616..5b7c944 100644 --- a/src/hz_sophia.erl +++ b/src/hz_sophia.erl @@ -41,8 +41,9 @@ next_token(Tk, [N | _] = String) when N >= $a, N =< $z -> alphanum_token(Tk, Tk, String, []); next_token(Tk, [$_ | _] = String) -> alphanum_token(Tk, Tk, String, []); -next_token({tk, Row, Col}, [Char | _]) -> - {error, {unknown_char, Row, Col, [Char]}}. +next_token({tk, Row, Col}, [Char | Rest]) -> + Token = {character, [Char], Row, Col, Col}, + {ok, {Token, {tk, Row + 1, Col}, Rest}}. num_token(Start, {tk, Row, Col}, [N | Rest], Acc) when N >= $0, N =< $9 -> num_token(Start, {tk, Row + 1, Col}, Rest, [N | Acc]); @@ -89,19 +90,58 @@ parse_expression(Type, Tk, String) -> parse_expression2(Type, Tk, String, {integer, S, Row, Start, End}) -> Value = list_to_integer(S), - check_type(integer, Type, Row, Start, End, {Value, Tk, String}); + case Type of + {_, _, integer} -> + {ok, {Value, Tk, String}}; + {_, _, unknown_type} -> + {ok, {Value, Tk, String}}; + {O, N, _} -> + {error, {wrong_type, O, N, integer, Row, Start, End}} + end; +parse_expression2(Type, Tk, String, {character, "[", Row, Start, _}) -> + parse_list(Type, Tk, String, Row, Start); parse_expression2(_, _, _, {_, S, Row, Start, End}) -> {error, {unexpected_token, S, Row, Start, End}}. -check_type(Expected, {_, _, Expected}, _, _, _, Result) -> - {ok, Result}; -check_type(_, {_, _, unknown_type}, _, _, _, Result) -> - % We want it to be possible to opt out of type-checking, since FATE is - % dynamically typed anyway. - {ok, Result}; -check_type(Expected, {O, N, _}, Row, Start, End, _) -> - {error, {wrong_type, O, N, Expected, Row, Start, End}}. +parse_list({_, _, {list, [Inner]}}, Tk, String, Row, Start) -> + parse_list_loop(Inner, Tk, String, Row, Start, []); +parse_list({_, _, unknown_type}, Tk, String, Row, Start) -> + parse_list_loop(unknown_type(), Tk, String, Row, Start, []); +parse_list({O, N, _}, _, _, Row, Start) -> + {error, {wrong_type, O, N, list, Row, Start, Start}}. +parse_list_loop(Inner, Tk, String, Row, Start, Acc) -> + case next_token(Tk, String) of + {ok, {{character, "]", _, _, _}, NewTk, NewString}} -> + {ok, {lists:reverse(Acc), NewTk, NewString}}; + {ok, {Token, NewTk, NewString}} -> + parse_list_loop2(Inner, NewTk, NewString, Row, Start, Acc, Token) + end. + +parse_list_loop2(Inner, Tk, String, Row, Start, Acc, Token) -> + case parse_expression2(Inner, Tk, String, Token) of + {ok, {Value, NewTk, NewString}} -> + parse_list_loop3(Inner, NewTk, NewString, Row, Start, [Value | Acc]); + {error, Reason} -> + Wrapped = wrap_error(Reason, {list_element, length(Acc)}), + {error, Wrapped} + end. + +parse_list_loop3(Inner, Tk, String, Row, Start, Acc) -> + case next_token(Tk, String) of + {ok, {{character, "]", _, _, _}, NewTk, NewString}} -> + {ok, {lists:reverse(Acc), NewTk, NewString}}; + {ok, {{character, ",", _, _, _}, NewTk, NewString}} -> + parse_list_loop(Inner, NewTk, NewString, Row, Start, Acc); + {error, Reason} -> + {error, Reason} + end. + +unknown_type() -> + {unknown_type, already_normalized, unknown_type}. + +% TODO +wrap_error(Reason, _) -> Reason. %%% Tests @@ -115,16 +155,23 @@ check_sophia_to_fate(Type, Sophia, Fate) -> end. check_parser(Type, Sophia, Fate) -> - UnknownType = setelement(3, Type, unknown_type), check_sophia_to_fate(Type, Sophia, Fate), - check_sophia_to_fate(UnknownType, Sophia, Fate), + check_sophia_to_fate(unknown_type(), Sophia, Fate), % Finally, check that the FATE result is something that gmb understands. gmb_fate_encoding:serialize(Fate), ok. -int_test() -> - {ok, Type} = hz_aaci:annotate_type(integer, #{}), - check_parser(Type, "123", 123). +check_parser(Sophia, Fate) -> + Source = "contract C = entrypoint f() = " ++ Sophia, + {ok, AACI} = hz_aaci:aaci_from_string(Source), + {ok, {_, Type}} = hz_aaci:get_function_signature(AACI, "f"), + check_parser(Type, Sophia, Fate). + +int_test() -> + check_parser("123", 123). + +list_test() -> + check_parser("[1, 2, 3]", [1, 2, 3]).