diff --git a/src/hz_sophia.erl b/src/hz_sophia.erl index 9254635..f81d377 100644 --- a/src/hz_sophia.erl +++ b/src/hz_sophia.erl @@ -4,26 +4,28 @@ -copyright("Jarvis Carroll "). -license("GPL-3.0-or-later"). --export([parse_literal/1, parse_literal/2]). +-export([parse_literal/2, parse_literal/1]). -export([fate_to_list/1, fate_to_list/2, fate_to_iolist/1, fate_to_iolist/2]). -include_lib("eunit/include/eunit.hrl"). - --spec parse_literal(Sophia) -> {ok, FATE} | {error, Reason} - when Sophia :: string(), - FATE :: gmb_fate_data:fate_type(), - Reason :: term(). - -parse_literal(String) -> - parse_literal(unknown_type(), String). - -spec parse_literal(Type, Sophia) -> {ok, FATE} | {error, Reason} when Type :: hz_aaci:annotated_type(), Sophia :: string(), FATE :: gmb_fate_data:fate_type(), Reason :: term(). +%% @doc +%% Parse a typed Sophia expression into a FATE term +%% The Sophia expression must consist only of literals, thus making a 'Sophia +%% term', which means no arithmetic, no function calls, no variables, etc. +%% The FATE term is in the format that gmbytecode expects as input, for forming +%% contract calls, etc. Used by the hz module to implement the 'sophia' format. +%% +%% The function takes type information retrieved from the AACI data structure, +%% which is used to interpret record types and variant types, but is also used +%% to check inputs and generate errors. + parse_literal(Type, String) -> case parse_expression(Type, {1, 1}, String) of {ok, {Result, NewPos, NewString}} -> @@ -43,6 +45,28 @@ parse_literal2(Result, Pos, String) -> {error, Reason} end. + +-spec parse_literal(Sophia) -> {ok, FATE} | {error, Reason} + when Sophia :: string(), + FATE :: gmb_fate_data:fate_type(), + Reason :: term(). + +%% @doc +%% Parse an untyped Sophia expression into a FATE term +%% Like parse_literal/2, but will not produce type errors. This function can +%% still produce parsing errors, and can produce errors when variants or +%% records are encountered, since they can't be parsed unless you have type +%% information. +%% +%% Note that since records are implemented as tuples, if you are trying to call +%a function that you know takes a record, but you don't have type information +%% available in the context where the expression is being passed, then tuples +%% can be used instead. This does not work if you have type information, +%% though, as tuples and records are different Sophia/AACI types. + +parse_literal(String) -> + parse_literal(unknown_type(), String). + %%% Tokenizer -define(IS_LATIN_UPPER(C), (((C) >= $A) and ((C) =< $Z))). @@ -927,6 +951,19 @@ wrap_error(Reason, _) -> Reason. when FATE :: gmb_fate_data:fate_type(), Sophia :: string(). +%% @doc +%% Print a FATE term from gmbytecode in Sophia syntax +%% FATE terms usually come from using gmbytecode to decode the result of an +%% on-chain transaction. +%% +%% This function does not use any type information to interpret the data, and +%% so can make mistakes. It's okay for interpreting tuples, lists, maps, +%% integers, and strings, but it will misinterpret the types of records and +%% unicode characters, and will crash the process if variants are encountered. +%% +%% fate_to_list/2 should be used whenever possible, especially since +%% transaction results are type checked by nodes at runtime. + fate_to_list(Term) -> fate_to_list(unknown_type(), Term). @@ -935,10 +972,27 @@ fate_to_list(Term) -> FATE :: gmb_fate_data:fate_type(), Sophia :: string(). + +%% @doc +%% Print a FATE term from gmbytecode in Sophia syntax +%% Like fate_to_list/1, but now type information from the AACI data structure +%% can be provided, in order to correctly interpret types like records, +%% variants, and unicode characters. If the type information you provide is +%% incorrect for the FATE term provided, then the function will fall back to +%% untyped pretty printing like in fate_to_list/1, but this is not recommended, +%% as correct type information should always be available. + fate_to_list(Type, Term) -> IOList = fate_to_iolist(Type, Term), unicode:characters_to_list(IOList). +%% @doc +%% Print a FATE term in Sophia syntax, without concatenating +%% The fate_to_list/1 function builds an iolist, and then concatenates it into +%% a list. If you are going to put the term into a bigger iolist directly +%% after, or write it to a streaming device, then it can save effort and memory +%% to just use the iolist directly. + -spec fate_to_iolist(FATE) -> Sophia when FATE :: gmb_fate_data:fate_type(), Sophia :: iolist(). @@ -951,6 +1005,11 @@ fate_to_iolist(Term) -> FATE :: gmb_fate_data:fate_type(), Sophia :: iolist(). +%% @doc +%% Print a FATE term in Sophia syntax, without concatenating +%% Prints using type information, like fate_to_list/2, but without spending +%% time or memory concatenating the result into a list, like fate_to_iolist/1. + % Special case for singleton records, since they are erased during compilation. fate_to_iolist({_, _, {record, [{FieldName, FieldType}]}}, Term) -> singleton_record_to_iolist(FieldName, FieldType, Term);