3 Commits

Author SHA1 Message Date
Jarvis Carroll 6daad4974c unwrap fate_to_erlang results
fate_to_erlang can only really fail at runtime if the wrong AACI is
provided, in which case the details of how failure occured are not
helpful, or recoverable. Anything else will be so broken that dialyzer
will catch it, or is a bug in hakuzaru, that we want to know about.
2026-06-08 07:23:34 +00:00
Jarvis Carroll d323fb0f52 Add special anonymous variant syntax
This is outside of the scope of the sophia parser, but is a simple generalization to
'sophia terms' to make them able to represent any FATE term anonymously.

We also parse these anonymous variant expressions without type info, since it is convenient
for users to copy the output of one call into another call.

Anonymous parsing of None and Some was also added, since new users would be shocked if this
doesn't work, and advanced users will greatly appreciate that it does. The resulting FATE
terms are still rendered as variant([0, 1], ...), since user defined types can also have [0, 1]
as their arity list, and since automation and tooling programmers hate special case exceptions like that.

Anonymous parsing of other Chain and AENS terms are not added, since anonymous variants already cover those types,
so very little is gained by hard-coding such complex types into the term parser. Complex, version-specific compiler
types are already supported by hakuzaru, in the form of the ACI/AACI; parsing without AACI, on the other hand, is
intended to support language-agnostic communication using the primitives of FATE, and in general, variants
in FATE are anonymous.
2026-06-05 03:08:38 +00:00
Jarvis Carroll ea3a5453f2 fix bytes coerce logic 2026-05-28 00:41:51 +00:00
2 changed files with 308 additions and 214 deletions
+135 -180
View File
@@ -527,10 +527,7 @@ opaque_type(Params, #{record := FieldDefs}) ->
|| #{name := Name, type := Type} <- FieldDefs], || #{name := Name, type := Type} <- FieldDefs],
{record, Fields}; {record, Fields};
opaque_type(Params, #{variant := VariantDefs}) -> opaque_type(Params, #{variant := VariantDefs}) ->
ConvertVariant = fun(Pair) -> ConvertVariant = fun(Pair) -> opaque_variant_each(Params, Pair) end,
[{Name, Types}] = maps:to_list(Pair),
{binary_to_list(Name), [opaque_type(Params, Type) || Type <- Types]}
end,
Variants = lists:map(ConvertVariant, VariantDefs), Variants = lists:map(ConvertVariant, VariantDefs),
{variant, Variants}; {variant, Variants};
opaque_type(Params, #{tuple := TypeDefs}) -> opaque_type(Params, #{tuple := TypeDefs}) ->
@@ -541,6 +538,11 @@ opaque_type(Params, Pair) when is_map(Pair) ->
[{Name, TypeArgs}] = maps:to_list(Pair), [{Name, TypeArgs}] = maps:to_list(Pair),
{opaque_type_name(Name), [opaque_type(Params, Arg) || Arg <- TypeArgs]}. {opaque_type_name(Name), [opaque_type(Params, Arg) || Arg <- TypeArgs]}.
opaque_variant_each(Params, Pair) ->
[{Name, Types}] = maps:to_list(Pair),
ElemTypes = [opaque_type(Params, Type) || Type <- Types],
{binary_to_list(Name), ElemTypes}.
-spec opaque_type_name(binary()) -> atom() | string(). -spec opaque_type_name(binary()) -> atom() | string().
% Atoms for any builtins that aren't qualified by a namespace in Sophia. % Atoms for any builtins that aren't qualified by a namespace in Sophia.
@@ -848,7 +850,7 @@ erlang_args_to_fate(VarTypes, Terms) ->
DefLength = length(VarTypes), DefLength = length(VarTypes),
ArgLength = length(Terms), ArgLength = length(Terms),
if if
DefLength =:= ArgLength -> coerce_zipped_bindings(lists:zip(VarTypes, Terms), to_fate, arg); DefLength =:= ArgLength -> coerce_zipped_bindings(lists:zip(VarTypes, Terms), arg);
DefLength > ArgLength -> {error, too_few_args}; DefLength > ArgLength -> {error, too_few_args};
DefLength < ArgLength -> {error, too_many_args} DefLength < ArgLength -> {error, too_many_args}
end. end.
@@ -926,7 +928,10 @@ erlang_to_fate({O, N, char}, Str) ->
single_error({invalid, O, N, Str}) single_error({invalid, O, N, Str})
end; end;
erlang_to_fate({O, N, {bytes, [Count]}}, Bytes) when is_bitstring(Bytes) -> erlang_to_fate({O, N, {bytes, [Count]}}, Bytes) when is_bitstring(Bytes) ->
coerce_bytes(O, N, Count, Bytes); case check_bytes(O, N, Count, Bytes) of
ok -> {ok, {bytes, Bytes}};
{error, Reason} -> {error, Reason}
end;
erlang_to_fate({_, _, bits}, Num) when is_integer(Num) -> erlang_to_fate({_, _, bits}, Num) when is_integer(Num) ->
{ok, {bits, Num}}; {ok, {bits, Num}};
erlang_to_fate({_, _, bits}, Bits) when is_bitstring(Bits) -> erlang_to_fate({_, _, bits}, Bits) when is_bitstring(Bits) ->
@@ -934,19 +939,19 @@ erlang_to_fate({_, _, bits}, Bits) when is_bitstring(Bits) ->
<<IntValue:Size>> = Bits, <<IntValue:Size>> = Bits,
{ok, {bits, IntValue}}; {ok, {bits, IntValue}};
erlang_to_fate({_, _, {list, [Type]}}, Data) when is_list(Data) -> erlang_to_fate({_, _, {list, [Type]}}, Data) when is_list(Data) ->
coerce_list(Type, Data, to_fate); coerce_list(Type, Data);
erlang_to_fate({_, _, {map, [KeyType, ValType]}}, Data) when is_map(Data) -> erlang_to_fate({_, _, {map, [KeyType, ValType]}}, Data) when is_map(Data) ->
coerce_map(KeyType, ValType, Data, to_fate); coerce_map(KeyType, ValType, Data);
erlang_to_fate({O, N, {tuple, ElementTypes}}, Data) when is_tuple(Data) -> erlang_to_fate({O, N, {tuple, ElementTypes}}, Data) when is_tuple(Data) ->
ElementList = tuple_to_list(Data), ElementList = tuple_to_list(Data),
coerce_tuple(O, N, ElementTypes, ElementList, to_fate); coerce_tuple(O, N, ElementTypes, ElementList);
erlang_to_fate({O, N, {variant, Variants}}, Name) when is_list(Name) -> erlang_to_fate({O, N, {variant, Variants}}, Name) when is_list(Name) ->
erlang_to_fate({O, N, {variant, Variants}}, {Name}); erlang_to_fate({O, N, {variant, Variants}}, {Name});
erlang_to_fate({O, N, {variant, Variants}}, Data) when is_tuple(Data), tuple_size(Data) > 0 -> erlang_to_fate({O, N, {variant, Variants}}, Data) when is_tuple(Data), tuple_size(Data) > 0 ->
[Name | Terms] = tuple_to_list(Data), [Name | Terms] = tuple_to_list(Data),
case lookup_variant(Name, Variants) of case lookup_variant(Name, Variants) of
{Tag, TermTypes} -> {Tag, TermTypes} ->
coerce_variant2(O, N, Variants, Name, Tag, TermTypes, Terms, to_fate); coerce_variant2(O, N, Variants, Name, Tag, TermTypes, Terms);
not_found -> not_found ->
ValidNames = [Valid || {Valid, _} <- Variants], ValidNames = [Valid || {Valid, _} <- Variants],
single_error({invalid_variant, O, N, Name, ValidNames}) single_error({invalid_variant, O, N, Name, ValidNames})
@@ -954,17 +959,15 @@ erlang_to_fate({O, N, {variant, Variants}}, Data) when is_tuple(Data), tuple_siz
erlang_to_fate({O, N, {record, MemberTypes}}, Map) when is_map(Map) -> erlang_to_fate({O, N, {record, MemberTypes}}, Map) when is_map(Map) ->
coerce_map_to_record(O, N, MemberTypes, Map); coerce_map_to_record(O, N, MemberTypes, Map);
erlang_to_fate({O, N, {unknown_type, _}}, Data) -> erlang_to_fate({O, N, {unknown_type, _}}, Data) ->
case N of warn_unknown_type(O, N, Data),
already_normalized ->
Message = "Warning: Unknown type ~p. Using term ~p as is.~n",
io:format(Message, [O, Data]);
_ ->
Message = "Warning: Unknown type ~p (i.e. ~p). Using term ~p as is.~n",
io:format(Message, [O, N, Data])
end,
{ok, Data}; {ok, Data};
erlang_to_fate({O, N, _}, Data) -> single_error({invalid, O, N, Data}). erlang_to_fate({O, N, _}, Data) -> single_error({invalid, O, N, Data}).
warn_unknown_type(O, already_normalized, Data) ->
io:format("Warning: Unknown type ~p. Using term ~p as is.~n", [O, Data]);
warn_unknown_type(O, N, Data) ->
io:format("Warning: Unknown type ~p (i.e. ~p). Using term ~p as is.~n", [O, N, Data]).
coerce_chain_object(_, _, _, _, {raw, Binary}) -> coerce_chain_object(_, _, _, _, {raw, Binary}) ->
{ok, Binary}; {ok, Binary};
coerce_chain_object(O, N, T, Tag, S) -> coerce_chain_object(O, N, T, Tag, S) ->
@@ -988,78 +991,78 @@ decode_chain_object(Tag, S) ->
error:incorrect_size -> {error, incorrect_size} error:incorrect_size -> {error, incorrect_size}
end. end.
coerce_bytes(O, N, _, Bytes) when bit_size(Bytes) rem 8 /= 0 -> check_bytes(O, N, _, Bytes) when bit_size(Bytes) rem 8 /= 0 ->
single_error({partial_bytes, O, N, bit_size(Bytes)}); single_error({partial_bytes, O, N, bit_size(Bytes)});
coerce_bytes(_, _, any, Bytes) -> check_bytes(_, _, any, _) ->
{ok, Bytes}; ok;
coerce_bytes(O, N, Count, Bytes) when byte_size(Bytes) /= Count -> check_bytes(O, N, Count, Bytes) when byte_size(Bytes) /= Count ->
single_error({incorrect_size, O, N, Bytes}); single_error({incorrect_size, O, N, Bytes});
coerce_bytes(_, _, _, Bytes) -> check_bytes(_, _, _, _) ->
{ok, Bytes}. ok.
coerce_zipped_bindings(Bindings, Direction, Tag) -> coerce_zipped_bindings(Bindings, Tag) ->
coerce_zipped_bindings(Bindings, Direction, Tag, [], []). coerce_zipped_bindings(Bindings, Tag, [], []).
coerce_zipped_bindings([Next | Rest], Direction, Tag, Good, Broken) -> coerce_zipped_bindings([Next | Rest], Tag, Good, Broken) ->
{{ArgName, Type}, Term} = Next, {{ArgName, Type}, Term} = Next,
case coerce_direction(Type, Term, Direction) of case erlang_to_fate(Type, Term) of
{ok, NewTerm} -> {ok, NewTerm} ->
coerce_zipped_bindings(Rest, Direction, Tag, [NewTerm | Good], Broken); coerce_zipped_bindings(Rest, Tag, [NewTerm | Good], Broken);
{error, Errors} -> {error, Errors} ->
Wrapped = wrap_errors({Tag, ArgName}, Errors), Wrapped = wrap_errors({Tag, ArgName}, Errors),
coerce_zipped_bindings(Rest, Direction, Tag, Good, [Wrapped | Broken]) coerce_zipped_bindings(Rest, Tag, Good, [Wrapped | Broken])
end; end;
coerce_zipped_bindings([], _, _, Good, []) -> coerce_zipped_bindings([], _, Good, []) ->
{ok, lists:reverse(Good)}; {ok, lists:reverse(Good)};
coerce_zipped_bindings([], _, _, _, Broken) -> coerce_zipped_bindings([], _, _, Broken) ->
{error, combine_errors(Broken)}. {error, combine_errors(Broken)}.
coerce_list(Type, Elements, Direction) -> coerce_list(Type, Elements) ->
% 0 index since it represents a sophia list % 0 index since it represents a sophia list
coerce_list(Type, Elements, Direction, 0, [], []). coerce_list(Type, Elements, 0, [], []).
coerce_list(Type, [Next | Rest], Direction, Index, Good, Broken) -> coerce_list(Type, [Next | Rest], Index, Good, Broken) ->
case coerce_direction(Type, Next, Direction) of case erlang_to_fate(Type, Next) of
{ok, Coerced} -> coerce_list(Type, Rest, Direction, Index + 1, [Coerced | Good], Broken); {ok, Coerced} -> coerce_list(Type, Rest, Index + 1, [Coerced | Good], Broken);
{error, Errors} -> {error, Errors} ->
Wrapped = wrap_errors({index, Index}, Errors), Wrapped = wrap_errors({index, Index}, Errors),
coerce_list(Type, Rest, Direction, Index + 1, Good, [Wrapped | Broken]) coerce_list(Type, Rest, Index + 1, Good, [Wrapped | Broken])
end; end;
coerce_list(_Type, [], _, _, Good, []) -> coerce_list(_Type, [], _, Good, []) ->
{ok, lists:reverse(Good)}; {ok, lists:reverse(Good)};
coerce_list(_, [], _, _, _, Broken) -> coerce_list(_, [], _, _, Broken) ->
{error, combine_errors(Broken)}. {error, combine_errors(Broken)}.
coerce_map(KeyType, ValType, Data, Direction) -> coerce_map(KeyType, ValType, Data) ->
coerce_map(KeyType, ValType, maps:iterator(Data), Direction, #{}, []). coerce_map(KeyType, ValType, maps:iterator(Data), #{}, []).
coerce_map(KeyType, ValType, Remaining, Direction, Good, Broken) -> coerce_map(KeyType, ValType, Remaining, Good, Broken) ->
case maps:next(Remaining) of case maps:next(Remaining) of
{K, V, RemainingAfter} -> {K, V, RemainingAfter} ->
coerce_map2(KeyType, ValType, RemainingAfter, Direction, Good, Broken, K, V); coerce_map2(KeyType, ValType, RemainingAfter, Good, Broken, K, V);
none -> none ->
coerce_map_finish(Good, Broken) coerce_map_finish(Good, Broken)
end. end.
coerce_map2(KeyType, ValType, Remaining, Direction, Good, Broken, K, V) -> coerce_map2(KeyType, ValType, Remaining, Good, Broken, K, V) ->
case coerce_direction(KeyType, K, Direction) of case erlang_to_fate(KeyType, K) of
{ok, KFATE} -> {ok, KFATE} ->
coerce_map3(KeyType, ValType, Remaining, Direction, Good, Broken, K, V, KFATE); coerce_map3(KeyType, ValType, Remaining, Good, Broken, K, V, KFATE);
{error, Errors} -> {error, Errors} ->
Wrapped = wrap_errors(map_key, Errors), Wrapped = wrap_errors(map_key, Errors),
% Continue as if the key coerced successfully, so that we can give % Continue as if the key coerced successfully, so that we can give
% errors for both the key and the value. % errors for both the key and the value.
coerce_map3(KeyType, ValType, Remaining, Direction, Good, [Wrapped | Broken], K, V, error) coerce_map3(KeyType, ValType, Remaining, Good, [Wrapped | Broken], K, V, error)
end. end.
coerce_map3(KeyType, ValType, Remaining, Direction, Good, Broken, K, V, KFATE) -> coerce_map3(KeyType, ValType, Remaining, Good, Broken, K, V, KFATE) ->
case coerce_direction(ValType, V, Direction) of case erlang_to_fate(ValType, V) of
{ok, VFATE} -> {ok, VFATE} ->
NewGood = Good#{KFATE => VFATE}, NewGood = Good#{KFATE => VFATE},
coerce_map(KeyType, ValType, Remaining, Direction, NewGood, Broken); coerce_map(KeyType, ValType, Remaining, NewGood, Broken);
{error, Errors} -> {error, Errors} ->
Wrapped = wrap_errors({map_value, K}, Errors), Wrapped = wrap_errors({map_value, K}, Errors),
coerce_map(KeyType, ValType, Remaining, Direction, Good, [Wrapped | Broken]) coerce_map(KeyType, ValType, Remaining, Good, [Wrapped | Broken])
end. end.
coerce_map_finish(Good, []) -> coerce_map_finish(Good, []) ->
@@ -1076,13 +1079,10 @@ lookup_variant(Name, [_ | Rest], Tag) ->
lookup_variant(_Name, [], _Tag) -> lookup_variant(_Name, [], _Tag) ->
not_found. not_found.
coerce_tuple(O, N, TermTypes, Terms, Direction) -> coerce_tuple(O, N, TermTypes, Terms) ->
case coerce_tuple_elements(TermTypes, Terms, Direction, tuple_element) of case coerce_elems_to_fate(TermTypes, Terms, tuple_element) of
{ok, Converted} -> {ok, Converted} ->
case Direction of {ok, {tuple, list_to_tuple(Converted)}};
to_fate -> {ok, {tuple, list_to_tuple(Converted)}};
from_fate -> {ok, list_to_tuple(Converted)}
end;
{error, too_few_terms} -> {error, too_few_terms} ->
single_error({tuple_too_few_terms, O, N, list_to_tuple(Terms)}); single_error({tuple_too_few_terms, O, N, list_to_tuple(Terms)});
{error, too_many_terms} -> {error, too_many_terms} ->
@@ -1090,19 +1090,14 @@ coerce_tuple(O, N, TermTypes, Terms, Direction) ->
Errors -> Errors Errors -> Errors
end. end.
coerce_variant2(O, N, Variants, Name, Tag, TermTypes, Terms, Direction) -> coerce_variant2(O, N, Variants, Name, Tag, TermTypes, Terms) ->
% FIXME: we could go through and add the variant tag to the adt_element % FIXME: we could go through and add the variant tag to the adt_element
% paths? % paths?
case coerce_tuple_elements(TermTypes, Terms, Direction, adt_element) of case coerce_elems_to_fate(TermTypes, Terms, adt_element) of
{ok, Converted} -> {ok, Converted} ->
case Direction of Arities = [length(VariantTerms)
to_fate -> || {_, VariantTerms} <- Variants],
Arities = [length(VariantTerms) {ok, {variant, Arities, Tag, list_to_tuple(Converted)}};
|| {_, VariantTerms} <- Variants],
{ok, {variant, Arities, Tag, list_to_tuple(Converted)}};
from_fate ->
{ok, list_to_tuple([Name | Converted])}
end;
{error, too_few_terms} -> {error, too_few_terms} ->
single_error({adt_too_few_terms, O, N, Name, TermTypes, Terms}); single_error({adt_too_few_terms, O, N, Name, TermTypes, Terms});
{error, too_many_terms} -> {error, too_many_terms} ->
@@ -1110,32 +1105,32 @@ coerce_variant2(O, N, Variants, Name, Tag, TermTypes, Terms, Direction) ->
Errors -> Errors Errors -> Errors
end. end.
coerce_tuple_elements(Types, Terms, Direction, Tag) -> coerce_elems_to_fate(Types, Terms, Tag) ->
% The sophia standard library uses 0 indexing for lists, and fst/snd/thd % The sophia standard library uses 0 indexing for lists, and fst/snd/thd
% for tuples... Not sure how we should report errors in tuples, then. % for tuples... Not sure how we should report errors in tuples, then.
coerce_tuple_elements(Types, Terms, Direction, Tag, 0, [], []). coerce_elems_to_fate(Types, Terms, Tag, 0, [], []).
coerce_tuple_elements([Type | Types], [Term | Terms], Direction, Tag, Index, Good, Broken) -> coerce_elems_to_fate([Type | Types], [Term | Terms], Tag, Index, Good, Broken) ->
case coerce_direction(Type, Term, Direction) of case erlang_to_fate(Type, Term) of
{ok, Value} -> {ok, Value} ->
coerce_tuple_elements(Types, Terms, Direction, Tag, Index + 1, [Value | Good], Broken); coerce_elems_to_fate(Types, Terms, Tag, Index + 1, [Value | Good], Broken);
{error, Errors} -> {error, Errors} ->
Wrapped = wrap_errors({Tag, Index}, Errors), Wrapped = wrap_errors({Tag, Index}, Errors),
coerce_tuple_elements(Types, Terms, Direction, Tag, Index + 1, Good, [Wrapped | Broken]) coerce_elems_to_fate(Types, Terms, Tag, Index + 1, Good, [Wrapped | Broken])
end; end;
coerce_tuple_elements([], [], _, _, _, Good, []) -> coerce_elems_to_fate([], [], _, _, Good, []) ->
{ok, lists:reverse(Good)}; {ok, lists:reverse(Good)};
coerce_tuple_elements([], [], _, _, _, _, Broken) -> coerce_elems_to_fate([], [], _, _, _, Broken) ->
{error, combine_errors(Broken)}; {error, combine_errors(Broken)};
coerce_tuple_elements(_, [], _, _, _, _, _) -> coerce_elems_to_fate(_, [], _, _, _, _) ->
{error, too_few_terms}; {error, too_few_terms};
coerce_tuple_elements([], _, _, _, _, _, _) -> coerce_elems_to_fate([], _, _, _, _, _) ->
{error, too_many_terms}. {error, too_many_terms}.
coerce_map_to_record(O, N, MemberTypes, Map) -> coerce_map_to_record(O, N, MemberTypes, Map) ->
case zip_record_fields(MemberTypes, Map) of case zip_record_fields(MemberTypes, Map) of
{ok, Zipped} -> {ok, Zipped} ->
case coerce_zipped_bindings(Zipped, to_fate, field) of case coerce_zipped_bindings(Zipped, field) of
{ok, [SingleElem]} -> {ok, [SingleElem]} ->
% Singleton records aren't implemented as FATE tuples at % Singleton records aren't implemented as FATE tuples at
% all. % all.
@@ -1152,31 +1147,6 @@ coerce_map_to_record(O, N, MemberTypes, Map) ->
single_error({unexpected_fields, O, N, Names}) single_error({unexpected_fields, O, N, Names})
end. end.
coerce_record_to_map(O, N, MemberTypes, Tuple) ->
{Names, Types} = lists:unzip(MemberTypes),
Terms = tuple_to_list(Tuple),
% FIXME: We could go through and change the record_element paths into field
% paths?
case coerce_tuple_elements(Types, Terms, from_fate, record_element) of
{ok, Converted} ->
Map = maps:from_list(lists:zip(Names, Converted)),
{ok, Map};
{error, too_few_terms} ->
single_error({record_too_few_terms, O, N, Tuple});
{error, too_many_terms} ->
single_error({record_too_many_terms, O, N, Tuple});
{error, Errors} ->
correct_record_error_paths(Names, Errors)
end.
correct_record_error_paths(Names, Errors) ->
CorrectOne = fun({Error, [{record_element, N} | Path]}) ->
FieldName = lists:nth(N + 1, Names),
{Error, [{record_element, N, FieldName} | Path]}
end,
Corrected = lists:map(CorrectOne, Errors),
{error, Corrected}.
zip_record_fields(Fields, Map) -> zip_record_fields(Fields, Map) ->
case lists:mapfoldl(fun zip_record_field/2, {Map, []}, Fields) of case lists:mapfoldl(fun zip_record_field/2, {Map, []}, Fields) of
{_, {_, Missing = [_|_]}} -> {_, {_, Missing = [_|_]}} ->
@@ -1217,20 +1187,10 @@ combine_errors(Broken) ->
%%% FATE to Erlang %%% FATE to Erlang
% Not sure if this is needed... fate_to_erlang shouldn't fail. -spec fate_to_erlang(Type, FATE) -> Erlang
coerce_direction(Type, Term, to_fate) ->
erlang_to_fate(Type, Term);
coerce_direction(Type, Term, from_fate) ->
fate_to_erlang(Type, Term).
-spec fate_to_erlang(Type, FATE) -> {ok, Erlang} | {error, Errors}
when Type :: annotated_type(), when Type :: annotated_type(),
FATE :: gmb_fate_data:fate_type(), FATE :: gmb_fate_data:fate_type(),
Erlang :: erlang_repr(), Erlang :: erlang_repr().
Errors :: [{Reason, [PathStep]}],
Reason :: term(),
PathStep :: term().
%% @doc %% @doc
%% Convert a FATE-flavored Erlang term into a Sophia-flavored Erlang term %% Convert a FATE-flavored Erlang term into a Sophia-flavored Erlang term
%% Typically this is called by hakuzaru for you when decoding results from the %% Typically this is called by hakuzaru for you when decoding results from the
@@ -1240,83 +1200,81 @@ coerce_direction(Type, Term, from_fate) ->
%% information. %% information.
fate_to_erlang({_, _, integer}, S) when is_integer(S) -> fate_to_erlang({_, _, integer}, S) when is_integer(S) ->
{ok, S}; S;
fate_to_erlang({_, _, address}, {address, Bin}) -> fate_to_erlang({_, _, address}, {address, Bin}) ->
Address = gmser_api_encoder:encode(account_pubkey, Bin), Address = gmser_api_encoder:encode(account_pubkey, Bin),
{ok, unicode:characters_to_list(Address)}; unicode:characters_to_list(Address);
fate_to_erlang({_, _, contract}, {contract, Bin}) -> fate_to_erlang({_, _, contract}, {contract, Bin}) ->
Address = gmser_api_encoder:encode(contract_pubkey, Bin), Address = gmser_api_encoder:encode(contract_pubkey, Bin),
{ok, unicode:characters_to_list(Address)}; unicode:characters_to_list(Address);
fate_to_erlang({_, _, signature}, Bin) -> fate_to_erlang({_, _, signature}, Bin) ->
Address = gmser_api_encoder:encode(signature, Bin), Address = gmser_api_encoder:encode(signature, Bin),
{ok, unicode:characters_to_list(Address)}; unicode:characters_to_list(Address);
%fate_to_erlang({_, _, channel}, {channel, S}) when is_binary(S) -> %fate_to_erlang({_, _, channel}, {channel, S}) when is_binary(S) ->
%{ok, S}; %S;
fate_to_erlang({_, _, boolean}, true) -> fate_to_erlang({_, _, boolean}, true) ->
{ok, true}; true;
fate_to_erlang({_, _, boolean}, false) -> fate_to_erlang({_, _, boolean}, false) ->
{ok, false}; false;
fate_to_erlang({_, _, string}, Bin) -> fate_to_erlang({_, _, string}, Bin) ->
Str = binary_to_list(Bin), binary_to_list(Bin);
{ok, Str};
fate_to_erlang({_, _, char}, Val) -> fate_to_erlang({_, _, char}, Val) ->
{ok, Val}; Val;
fate_to_erlang({O, N, {bytes, [Count]}}, Bytes) when is_bitstring(Bytes) -> fate_to_erlang({O, N, {bytes, [Count]}}, {bytes, Bytes}) when is_bitstring(Bytes) ->
coerce_bytes(O, N, Count, Bytes); case check_bytes(O, N, Count, Bytes) of
ok -> Bytes;
{error, Reason} -> erlang:exit(Reason)
end;
fate_to_erlang({_, _, bits}, {bits, Num}) -> fate_to_erlang({_, _, bits}, {bits, Num}) ->
{ok, Num}; Num;
fate_to_erlang({_, _, {list, [Type]}}, Data) when is_list(Data) -> fate_to_erlang({_, _, {list, [Type]}}, Data) when is_list(Data) ->
coerce_list(Type, Data, from_fate); Each = fun(Elem) -> fate_to_erlang(Type, Elem) end,
lists:map(Each, Data);
fate_to_erlang({_, _, {map, [KeyType, ValType]}}, Data) when is_map(Data) -> fate_to_erlang({_, _, {map, [KeyType, ValType]}}, Data) when is_map(Data) ->
coerce_map(KeyType, ValType, Data, from_fate); coerce_map_to_erlang(KeyType, ValType, maps:iterator(Data), #{});
fate_to_erlang({O, N, {tuple, ElementTypes}}, {tuple, Data}) -> fate_to_erlang({_, _, {tuple, ElementTypes}}, {tuple, Data}) ->
ElementList = tuple_to_list(Data), ElementList = tuple_to_list(Data),
coerce_tuple(O, N, ElementTypes, ElementList, from_fate); Elems = coerce_elems_to_erlang(ElementTypes, ElementList),
fate_to_erlang({O, N, {variant, Variants}}, {variant, _, Tag, Tuple}) -> list_to_tuple(Elems);
fate_to_erlang({_, _, {variant, Variants}}, {variant, _, Tag, Tuple}) ->
Terms = tuple_to_list(Tuple), Terms = tuple_to_list(Tuple),
{Name, TermTypes} = lists:nth(Tag + 1, Variants), {Name, Types} = lists:nth(Tag + 1, Variants),
coerce_variant2(O, N, Variants, Name, Tag, TermTypes, Terms, from_fate); Elems = coerce_elems_to_erlang(Types, Terms),
fate_to_erlang({O, N, {record, [SingleMemberType]}}, Data) -> list_to_tuple([Name | Elems]);
fate_to_erlang({_, _, {record, [SingleField]}}, Data) ->
% Singleton records aren't implemented as FATE tuples at all. % Singleton records aren't implemented as FATE tuples at all.
% Pretend they are, so we can get the full error indexing of the coerce_record_to_map([SingleField], [Data], #{});
% non-singletone case. fate_to_erlang({_, _, {record, MemberTypes}}, {tuple, Tuple}) ->
coerce_record_to_map(O, N, [SingleMemberType], {Data}); Terms = tuple_to_list(Tuple),
fate_to_erlang({O, N, {record, MemberTypes}}, {tuple, Tuple}) -> coerce_record_to_map(MemberTypes, Terms, #{});
coerce_record_to_map(O, N, MemberTypes, Tuple);
fate_to_erlang({O, N, {unknown_type, _}}, Data) -> fate_to_erlang({O, N, {unknown_type, _}}, Data) ->
case N of warn_unknown_type(O, N, Data),
already_normalized -> Data;
Message = "Warning: Unknown type ~p. Using term ~p as is.~n", fate_to_erlang({O, N, _}, Data) ->
io:format(Message, [O, Data]); erlang:exit({invalid, O, N, Data}).
_ ->
Message = "Warning: Unknown type ~p (i.e. ~p). Using term ~p as is.~n",
io:format(Message, [O, N, Data])
end,
{ok, Data};
fate_to_erlang(Type, Data) ->
TypeStr = type_to_iolist(Type),
io:format("Warning: Could not coerce term into ~s. Using term as is: ~p~n", [TypeStr, Data]),
{ok, Data}.
type_to_iolist({O, already_normalized, S}) -> coerce_elems_to_erlang(Types, Elems) ->
% Already normalized. Example output: Zipped = lists:zip(Types, Elems),
% type {map, [string, integer]} Each = fun({Type, Elem}) -> fate_to_erlang(Type, Elem) end,
opaque_type_to_iolist(O, S); lists:map(Each, Zipped).
type_to_iolist({O, N, S}) ->
% Type alias. Print the alias, and then print the normalized version in
% parentheses. Example output:
% type "my_alias" (i.e. record type {"my_record_type", [integer]})
io_lib:format("type ~p (i.e. ~s)", [O, opaque_type_to_iolist(N, S)]).
opaque_type_to_iolist(N, {record, _}) -> coerce_record_to_map([{Name, Type} | Types], [Term | Terms], Acc) ->
% N is the name of a record definition. Coerced = fate_to_erlang(Type, Term),
io_lib:format("record type ~p", [N]); NewAcc = maps:put(Name, Coerced, Acc),
opaque_type_to_iolist(N, {variant, _}) -> coerce_record_to_map(Types, Terms, NewAcc);
% N is the name of a variant definition. coerce_record_to_map([], [], Acc) ->
io_lib:format("variant type ~p", [N]); Acc.
opaque_type_to_iolist(N, _) ->
% N is some other constructive type. coerce_map_to_erlang(KeyType, ValType, Iter, Acc) ->
io_lib:format("type ~p", [N]). case maps:next(Iter) of
{KeyFATE, ValFATE, Rest} ->
Key = fate_to_erlang(KeyType, KeyFATE),
Val = fate_to_erlang(ValType, ValFATE),
NewAcc = maps:put(Key, Val, Acc),
coerce_map_to_erlang(KeyType, ValType, Rest, NewAcc);
none ->
Acc
end.
@@ -1354,7 +1312,7 @@ check_erlang_to_fate(Type, Sophia, Fate) ->
end. end.
check_fate_to_erlang(Type, Fate, Sophia) -> check_fate_to_erlang(Type, Fate, Sophia) ->
{ok, SophiaActual} = fate_to_erlang(Type, Fate), SophiaActual = fate_to_erlang(Type, Fate),
% Now check that the results were what we expected. % Now check that the results were what we expected.
case SophiaActual of case SophiaActual of
Sophia -> Sophia ->
@@ -1452,7 +1410,7 @@ coerce_record_test() ->
coerce_bytes_test() -> coerce_bytes_test() ->
{ok, Type} = annotate_type({tuple, [{bytes, [4]}, {bytes, [any]}]}, #{}), {ok, Type} = annotate_type({tuple, [{bytes, [4]}, {bytes, [any]}]}, #{}),
check_roundtrip(Type, {<<"abcd">>, <<"efghi">>}, {tuple, {<<"abcd">>, <<"efghi">>}}). check_roundtrip(Type, {<<"abcd">>, <<"efghi">>}, {tuple, {{bytes, <<"abcd">>}, {bytes, <<"efghi">>}}}).
coerce_bits_test() -> coerce_bits_test() ->
{ok, Type} = annotate_type(bits, #{}), {ok, Type} = annotate_type(bits, #{}),
@@ -1471,7 +1429,7 @@ coerce_unicode_test() ->
coerce_hash_test() -> coerce_hash_test() ->
{ok, Type} = annotate_type("hash", builtin_typedefs()), {ok, Type} = annotate_type("hash", builtin_typedefs()),
Hash = list_to_binary(lists:seq(1,32)), Hash = list_to_binary(lists:seq(1,32)),
check_roundtrip(Type, Hash, Hash), check_roundtrip(Type, Hash, {bytes, Hash}),
ok. ok.
@@ -1519,10 +1477,7 @@ singleton_record_substitution_test() ->
{ok, {[], GOutput}} = get_function_signature(AACI, "g"), {ok, {[], GOutput}} = get_function_signature(AACI, "g"),
check_roundtrip(GOutput, #{"it" => #{"it" => 123}}, 123), check_roundtrip(GOutput, #{"it" => #{"it" => 123}}, 123),
{ok, {[], HOutput}} = get_function_signature(AACI, "h"), {ok, {[], HOutput}} = get_function_signature(AACI, "h"),
check_roundtrip(HOutput, #{"it" => {123, 456}}, {tuple, {123, 456}}), check_roundtrip(HOutput, #{"it" => {123, 456}}, {tuple, {123, 456}}).
% Also check that records have accurate paths, since the implementation for
% record error paths is a bit fiddly.
{error, [{{tuple_too_many_terms, _, _, _}, [{record_element, 0, "it"}]}]} = fate_to_erlang(HOutput, {tuple, {1, 2, 3}}).
tuple_substitution_test() -> tuple_substitution_test() ->
Contract = " Contract = "
+173 -34
View File
@@ -343,6 +343,12 @@ parse_expression2(_, _, _, Token) ->
unknown_type() -> unknown_type() ->
{unknown_type, already_normalized, unknown_type}. {unknown_type, already_normalized, unknown_type}.
int_type() ->
{integer, already_normalized, integer}.
int_list_type() ->
{{list, [integer]}, alread_normalized, {list, [int_type()]}}.
expect_tokens([], Pos, String) -> expect_tokens([], Pos, String) ->
{ok, {Pos, String}}; {ok, {Pos, String}};
expect_tokens([Str | Rest], Pos, String) -> expect_tokens([Str | Rest], Pos, String) ->
@@ -377,11 +383,14 @@ parse_alphanum(Type, Pos, String, ["Bits", "all"], Row, Start, End) ->
typecheck_bits(Type, Pos, String, -1, Row, Start, End); typecheck_bits(Type, Pos, String, -1, Row, Start, End);
parse_alphanum(Type, Pos, String, ["Bits", "none"], Row, Start, End) -> parse_alphanum(Type, Pos, String, ["Bits", "none"], Row, Start, End) ->
typecheck_bits(Type, Pos, String, 0, Row, Start, End); typecheck_bits(Type, Pos, String, 0, Row, Start, End);
parse_alphanum(Type, Pos, String, ["variant"], Row, Start, End) ->
parse_anonymous_variant(Type, Pos, String, Row, Start, End);
parse_alphanum(Type, Pos, String, [[C | _] = S], Row, Start, End) when ?IS_LATIN_LOWER(C) -> parse_alphanum(Type, Pos, String, [[C | _] = S], Row, Start, End) when ?IS_LATIN_LOWER(C) ->
% From a programming perspective, we are trying to parse a constant, so % From a programming perspective, we are trying to parse a constant, so
% an alphanum token can really only be a constructor, or a chain object. % an alphanum token can really only be a constructor, or a chain object.
% Constructors start with uppercase characters, so lowercase can only be a % Constructors start with uppercase characters, and we have handled our
% chain object. % made-up 'variant' case explicitly, so the only other lowercase constants
% are serialized chain objects.
try try
case gmser_api_encoder:decode(unicode:characters_to_binary(S)) of case gmser_api_encoder:decode(unicode:characters_to_binary(S)) of
{account_pubkey, Data} -> {account_pubkey, Data} ->
@@ -400,8 +409,8 @@ parse_alphanum(Type, Pos, String, [[C | _] = S], Row, Start, End) when ?IS_LATIN
_:_ -> {error, {unexpected_identifier, S, Row, Start, End}} _:_ -> {error, {unexpected_identifier, S, Row, Start, End}}
end; end;
parse_alphanum(Type, Pos, String, Path, Row, Start, End) -> parse_alphanum(Type, Pos, String, Path, Row, Start, End) ->
% Inversely, chain object prefixes are always lowercase, so any other path % Now having handled all lowercase terms, anything else must be uppercase,
% must be a variant constructor, or invalid. % which is either a variant constructor, or totally invalid.
parse_variant(Type, Pos, String, Path, Row, Start, End). parse_variant(Type, Pos, String, Path, Row, Start, End).
typecheck_integer({_, _, integer}, Pos, String, Value, _, _, _) -> typecheck_integer({_, _, integer}, Pos, String, Value, _, _, _) ->
@@ -731,6 +740,12 @@ parse_variant({O, N, {variant, Variants}}, Pos, String, [Namespace, Constructor]
_ -> _ ->
{error, {invalid_constructor, O, N, Namespace ++ "." ++ Constructor, Row, Start, End}} {error, {invalid_constructor, O, N, Namespace ++ "." ++ Constructor, Row, Start, End}}
end; end;
parse_variant({_, _, unknown_type}, Pos, String, ["None"], _, _, _) ->
% Special case for None without type info.
parse_variant3([0, 1], 0, [], Pos, String);
parse_variant({_, _, unknown_type}, Pos, String, ["Some"], _, _, _) ->
% Also a special case for Some.
parse_variant3([0, 1], 1, [unknown_type()], Pos, String);
parse_variant({_, _, unknown_type}, _, _, _, Row, Start, End) -> parse_variant({_, _, unknown_type}, _, _, _, Row, Start, End) ->
{error, {unresolved_variant, Row, Start, End}}; {error, {unresolved_variant, Row, Start, End}};
parse_variant({O, N, _}, _, _, _, Row, Start, End) -> parse_variant({O, N, _}, _, _, _, Row, Start, End) ->
@@ -753,8 +768,7 @@ get_typename(Name) ->
parse_variant2(O, N, Variants, Pos, String, Prefix, Constructor, Row, Start, End) -> parse_variant2(O, N, Variants, Pos, String, Prefix, Constructor, Row, Start, End) ->
case lookup_variant(Constructor, Variants, 0) of case lookup_variant(Constructor, Variants, 0) of
{ok, {Tag, ElemTypes}} -> {ok, {Tag, ElemTypes}} ->
GetArity = fun({_, OtherElemTypes}) -> length(OtherElemTypes) end, Arities = get_arities(Variants),
Arities = lists:map(GetArity, Variants),
parse_variant3(Arities, Tag, ElemTypes, Pos, String); parse_variant3(Arities, Tag, ElemTypes, Pos, String);
error -> error ->
{error, {invalid_constructor, O, N, Prefix ++ Constructor, Row, Start, End}} {error, {invalid_constructor, O, N, Prefix ++ Constructor, Row, Start, End}}
@@ -790,6 +804,112 @@ lookup_variant(Ident, [{Ident, ElemTypes} | _], Tag) ->
lookup_variant(Ident, [_ | Rest], Tag) -> lookup_variant(Ident, [_ | Rest], Tag) ->
lookup_variant(Ident, Rest, Tag + 1). lookup_variant(Ident, Rest, Tag + 1).
get_arities(Variants) ->
GetArity = fun({_, OtherElemTypes}) -> length(OtherElemTypes) end,
lists:map(GetArity, Variants).
parse_anonymous_variant({O, N, {variant, Variants}}, Pos, String, _, _, _) ->
parse_anonymous_variant2({O, N, {variant, Variants}}, Pos, String);
parse_anonymous_variant({O, N, unknown_type}, Pos, String, _, _, _) ->
parse_anonymous_variant2({O, N, unknown_type}, Pos, String);
parse_anonymous_variant({O, N, _}, _, _, Row, Start, End) ->
{error, {wrong_type, O, N, variant, Row, Start, End}}.
parse_anonymous_variant2(Type, Pos, String) ->
case expect_tokens(["("], Pos, String) of
{ok, {NewPos, NewString}} ->
parse_anonymous_variant3(Type, NewPos, NewString);
{error, Reason} ->
{error, Reason}
end.
parse_anonymous_variant3(Type, Pos, String) ->
case parse_arities(Type, Pos, String) of
{ok, {Arities, NewPos, NewString}} ->
parse_anonymous_variant4(Type, NewPos, NewString, Arities);
{error, Reason} ->
{error, Reason}
end.
parse_anonymous_variant4(Type, Pos, String, Arities) ->
case expect_tokens([","], Pos, String) of
{ok, {NewPos, NewString}} ->
parse_anonymous_variant5(Type, NewPos, NewString, Arities);
{error, Reason} ->
{error, Reason}
end.
parse_anonymous_variant5(Type, Pos, String, Arities) ->
case parse_anonymous_tag(Pos, String, Arities) of
{ok, {Tag, NewPos, NewString}} ->
parse_anonymous_variant6(Type, NewPos, NewString, Arities, Tag);
{error, Reason} ->
{error, Reason}
end.
parse_anonymous_variant6(Type, Pos, String, Arities, Tag) ->
ElemTypes = infer_anonymous_variant_elem_types(Type, Arities, Tag),
case parse_multivalue3(ElemTypes, Pos, String, []) of
{ok, {Terms, NewPos, NewString}} ->
Result = {variant, Arities, Tag, list_to_tuple(Terms)},
{ok, {Result, NewPos, NewString}};
{error, Reason} ->
{error, Reason}
end.
parse_arities(Type, Pos, String) ->
case next_token(Pos, String) of
{ok, {Token, NewPos, NewString}} ->
parse_arities2(Type, NewPos, NewString, Token);
{error, Reason} ->
{error, Reason}
end.
parse_arities2(Type, Pos, String, Token = {_, _, _, Row, Start, _}) ->
case parse_expression2(int_list_type(), Pos, String, Token) of
{ok, {Arities, NewPos, NewString}} ->
parse_arities3(Type, NewPos, NewString, Arities, Row, Start);
{error, Reason} ->
{error, Reason}
end.
parse_arities3({O, N, {variant, Variants}}, Pos, String, Arities, Row, Start) ->
ExpectedArities = get_arities(Variants),
case Arities == ExpectedArities of
true ->
{ok, {Arities, Pos, String}};
false ->
{error, {wrong_arities, O, N, Arities, Row, Start}}
end;
parse_arities3(_, Pos, String, Arities, _, _) ->
{ok, {Arities, Pos, String}}.
parse_anonymous_tag(Pos, String, Arities) ->
case next_token(Pos, String) of
{ok, {Token, NewPos, NewString}} ->
parse_anonymous_tag2(NewPos, NewString, Arities, Token);
{error, Reason} ->
{error, Reason}
end.
parse_anonymous_tag2(Pos, String, Arities, Token = {_, _, _, Row, Start, End}) ->
TagCount = length(Arities),
case parse_expression2(int_type(), Pos, String, Token) of
{ok, {Tag, _, _}} when Tag < 0 ->
{error, {negative_tag, Tag, Row, Start, End}};
{ok, {Tag, _, _}} when Tag >= TagCount ->
{error, {invalid_tag, Tag, TagCount, Row, Start, End}};
Result ->
Result
end.
infer_anonymous_variant_elem_types({_, _, {variant, Variants}}, _, Tag) ->
{_Name, ElemTypes} = lists:nth(Tag + 1, Variants),
ElemTypes;
infer_anonymous_variant_elem_types({_, _, unknown_type}, Arities, Tag) ->
Arity = lists:nth(Tag + 1, Arities),
lists:duplicate(Arity, unknown_type()).
%%% Record parsing %%% Record parsing
parse_record_or_map({_, _, {map, [KeyType, ValueType]}}, Pos, String, _, _) -> parse_record_or_map({_, _, {map, [KeyType, ValueType]}}, Pos, String, _, _) ->
@@ -1027,15 +1147,12 @@ fate_to_iolist(Type, {tuple, Tuple}) ->
_ -> _ ->
tuple_to_iolist([], Tuple) tuple_to_iolist([], Tuple)
end; end;
fate_to_iolist(Type, {variant, _, Tag, Tuple}) -> fate_to_iolist(Type, {variant, Arities, Tag, Tuple}) ->
case Type of case Type of
{O, N, {variant, VariantTypes}} when Tag < length(VariantTypes) -> {O, N, {variant, VariantTypes}} when Tag < length(VariantTypes) ->
variant_to_iolist(O, N, VariantTypes, Tag, Tuple); variant_to_iolist(O, N, VariantTypes, Tag, Tuple);
{O, N, _} -> {_, _, _} ->
% TODO: Make up a special syntax for anonymous variant terms. anonymous_variant_to_iolist(Arities, Tag, Tuple)
erlang:exit({untyped_variant, O, N});
_ ->
erlang:exit({untyped_variant, unknown_type, already_normalized})
end; end;
fate_to_iolist(Type, List) when is_list(List) -> fate_to_iolist(Type, List) when is_list(List) ->
case Type of case Type of
@@ -1130,6 +1247,22 @@ choose_variant_prefix(O, N) ->
[] []
end. end.
% We don't have type information, but the Sophia programming language doesn't
% have syntax for anonymous variants, so we have to make a syntax up. This
% syntax is also supported when parsing terms, so that the output of one
% contract call can be fed easily into another contract call.
anonymous_variant_to_iolist(Arities, Tag, Tuple) ->
% Extract the elements of the tuple.
Elems = tuple_to_list(Tuple),
% Turn the arities, tag, and elements into an iolist.
AritiesStr = list_to_iolist(int_type(), Arities),
TagStr = integer_to_list(Tag),
FullTermsStr = list_elems_to_iolist(unknown_type(), Elems, [AritiesStr, ", ", TagStr]),
% Wrap that iolist in the anonymous 'variant' constructor.
["variant(", FullTermsStr, ")"].
multivalue_to_iolist([FirstType | ElemTypes], [FirstTerm | Elems]) -> multivalue_to_iolist([FirstType | ElemTypes], [FirstTerm | Elems]) ->
FirstTermChars = fate_to_iolist(FirstType, FirstTerm), FirstTermChars = fate_to_iolist(FirstType, FirstTerm),
multivalue_to_iolist(ElemTypes, Elems, FirstTermChars); multivalue_to_iolist(ElemTypes, Elems, FirstTermChars);
@@ -1282,16 +1415,18 @@ check_parser_roundtrip(Sophia) ->
% syntax. Let's do a lenient test. % syntax. Let's do a lenient test.
roundtrip_parser_lenient(unknown_type(), Sophia, Fate). roundtrip_parser_lenient(unknown_type(), Sophia, Fate).
check_parser_with_typedef(Typedef, Sophia) -> check_parser_with_typedef(Typedef, Sophia, UntypedSophia) ->
% Compile the type definitions alongside the usual literal expression. % Compile the type definitions alongside the usual literal expression.
Source = "contract C =\n " ++ Typedef ++ "\n entrypoint f() = " ++ Sophia, Source = "contract C =\n " ++ Typedef ++ "\n entrypoint f() = " ++ Sophia,
{Fate, Type} = compile_entrypoint_value_and_type(Source, "f"), {Fate, Type} = compile_entrypoint_value_and_type(Source, "f"),
% Do a typed parse, as usual, but there are probably record/variant % Do a typed parse, as usual. Variant namespaces can make pretty printing
% definitions in the AACI, so untyped parses probably don't work, and % ambiguous, so make the roundtrip lenient.
% variants often have optional namespaces, so the sophia result might not roundtrip_parser_lenient(Type, Sophia, Fate),
% match exactly, but should still be equivalent. % Do an untyped parse, but using a second special Sophia expression that
roundtrip_parser_lenient(Type, Sophia, Fate). % doesn't require type info to parse. This one *doesn't* need to be
% lenient, since we are specifying a distinct sophia expression.
roundtrip_parser(unknown_type(), UntypedSophia, Fate).
anon_types_test() -> anon_types_test() ->
% Integers. % Integers.
@@ -1323,6 +1458,10 @@ anon_types_test() ->
check_parser_roundtrip("(1, [2, 3], (4, 5))"), check_parser_roundtrip("(1, [2, 3], (4, 5))"),
% Map. % Map.
check_parser_roundtrip("{[1] = 2, [3] = 4}"), check_parser_roundtrip("{[1] = 2, [3] = 4}"),
% Option.
check_parser_roundtrip("None"),
check_parser_roundtrip("Some(1)"),
check_parser_roundtrip("Some([1, 2, 3])"),
ok. ok.
@@ -1342,7 +1481,7 @@ string_escape_codes_test() ->
records_test() -> records_test() ->
TypeDef = "record pair = {x: int, y: int}", TypeDef = "record pair = {x: int, y: int}",
Sophia = "{x = 1, y = 2}", Sophia = "{x = 1, y = 2}",
check_parser_with_typedef(TypeDef, Sophia), check_parser_with_typedef(TypeDef, Sophia, "(1, 2)"),
% The above won't run an untyped parse on the expression, but we can. It % The above won't run an untyped parse on the expression, but we can. It
% will error, though. % will error, though.
{error, {unresolved_record, _, _, _}} = parse_literal(unknown_type(), Sophia). {error, {unresolved_record, _, _, _}} = parse_literal(unknown_type(), Sophia).
@@ -1350,11 +1489,11 @@ records_test() ->
variant_test() -> variant_test() ->
TypeDef = "datatype multi('a) = Zero | One('a) | Two('a, 'a)", TypeDef = "datatype multi('a) = Zero | One('a) | Two('a, 'a)",
check_parser_with_typedef(TypeDef, "Zero"), check_parser_with_typedef(TypeDef, "Zero", "variant([0, 1, 2], 0)"),
check_parser_with_typedef(TypeDef, "One(0)"), check_parser_with_typedef(TypeDef, "One(0)", "variant([0, 1, 2], 1, 0)"),
check_parser_with_typedef(TypeDef, "Two(0, 1)"), check_parser_with_typedef(TypeDef, "Two(0, 1)", "variant([0, 1, 2], 2, 0, 1)"),
check_parser_with_typedef(TypeDef, "Two([], [1, 2, 3])"), check_parser_with_typedef(TypeDef, "Two([], [1, 2, 3])", "variant([0, 1, 2], 2, [], [1, 2, 3])"),
check_parser_with_typedef(TypeDef, "C.Zero"), check_parser_with_typedef(TypeDef, "C.Zero", "variant([0, 1, 2], 0)"),
{error, {unresolved_variant, _, _, _}} = parse_literal(unknown_type(), "Zero"), {error, {unresolved_variant, _, _, _}} = parse_literal(unknown_type(), "Zero"),
@@ -1362,10 +1501,10 @@ variant_test() ->
ambiguous_variant_test() -> ambiguous_variant_test() ->
TypeDef = "datatype mytype = C | D", TypeDef = "datatype mytype = C | D",
check_parser_with_typedef(TypeDef, "C"), check_parser_with_typedef(TypeDef, "C", "variant([0, 0], 0)"),
check_parser_with_typedef(TypeDef, "D"), check_parser_with_typedef(TypeDef, "D", "variant([0, 0], 1)"),
check_parser_with_typedef(TypeDef, "C.C"), check_parser_with_typedef(TypeDef, "C.C", "variant([0, 0], 0)"),
check_parser_with_typedef(TypeDef, "C.D"), check_parser_with_typedef(TypeDef, "C.D", "variant([0, 0], 1)"),
ok. ok.
@@ -1410,9 +1549,9 @@ bits_test() ->
singleton_records_test() -> singleton_records_test() ->
TypeDef = "record singleton('a) = {it: 'a}", TypeDef = "record singleton('a) = {it: 'a}",
check_parser_with_typedef(TypeDef, "{it = 123}"), check_parser_with_typedef(TypeDef, "{it = 123}", "123"),
check_parser_with_typedef(TypeDef, "{it = {it = {it = 5}}}"), check_parser_with_typedef(TypeDef, "{it = {it = {it = 5}}}", "5"),
check_parser_with_typedef(TypeDef, "[{it = 1}, {it = 2}, {it = 3}]"), check_parser_with_typedef(TypeDef, "[{it = 1}, {it = 2}, {it = 3}]", "[1, 2, 3]"),
ok. ok.
@@ -1421,9 +1560,9 @@ singleton_variants_test() ->
% actually a special case; singleton variants are in fact wrapped in the % actually a special case; singleton variants are in fact wrapped in the
% FATE too. % FATE too.
TypeDef = "datatype wrapped('a) = Wrap('a)", TypeDef = "datatype wrapped('a) = Wrap('a)",
check_parser_with_typedef(TypeDef, "Wrap(123)"), check_parser_with_typedef(TypeDef, "Wrap(123)", "variant([1], 0, 123)"),
check_parser_with_typedef(TypeDef, "Wrap(Wrap(123))"), check_parser_with_typedef(TypeDef, "Wrap(Wrap(123))", "variant([1], 0, variant([1], 0, 123))"),
check_parser_with_typedef(TypeDef, "[Wrap(1), Wrap(2), Wrap(3)]"), check_parser_with_typedef(TypeDef, "[Wrap(1), Wrap(2), Wrap(3)]", "[variant([1], 0, 1), variant([1], 0, 2), variant([1], 0, 3)]"),
ok. ok.