diff --git a/src/canary/metta_corelib.pl b/src/canary/metta_corelib.pl index 93b4cf387d6..165596727c7 100755 --- a/src/canary/metta_corelib.pl +++ b/src/canary/metta_corelib.pl @@ -135,6 +135,9 @@ metta_atom_corelib_types( [:, unify, [->, 'Atom', 'Atom', 'Atom', 'Atom', '%Undefined%']]). metta_atom_corelib_types( [:, unify, [->, 'Atom', 'Atom', 'Atom', 'Atom', 'Atom']]). metta_atom_corelib_types( [:, unquote, [->, '%Undefined%', '%Undefined%']]). +% metta_atom_corelib_types( [:, stringToChars [-> 'Atom' 'Expression']]). +% metta_atom_corelib_types( [:, charsToString [-> 'Expression' 'Atom']]). +% metta_atom_corelib_types( [:, format-args [-> 'Atom' 'Expression' 'Atom']]). metta_atom_corelib_types( [:, 'get-metatype', [->, 'Atom', 'Atom']]). metta_atom_corelib_types( [:, 'get-type0', [->, 'Atom', 'Atom']]). diff --git a/src/canary/metta_eval.pl b/src/canary/metta_eval.pl index e395efe1eac..36c4a414a17 100755 --- a/src/canary/metta_eval.pl +++ b/src/canary/metta_eval.pl @@ -1182,6 +1182,56 @@ eval_20(Eq,RetType,Depth,Self,['stringToChars',String],Chars):- !, eval_args(Eq,RetType,Depth,Self,String,SS), string_chars(SS,Chars0), maplist(as_metta_char,Chars0,Chars). eval_20(Eq,RetType,Depth,Self,['charsToString',Chars],String):- !, eval_args(Eq,RetType,Depth,Self,Chars,CC), maplist(as_metta_char,CC0,CC), string_chars(String,CC0). +% ================================================================= +% ================================================================= +% ================================================================= +% FORMAT-ARGS +% ================================================================= +% ================================================================= +% ================================================================= + +% We deal with indexing, but not formatting (the stuff following the ':')(yet) +% https://doc.rust-lang.org/std/fmt/ used as a reference + +format_args_get_index([C|FormatRest1], FormatRest2, Index2) :- char_code(C, Ccode), Ccode >= 48, Ccode =< 57, !, % in the range ['0'..'9'] + Index1 is Ccode-48, + format_args_get_index1(FormatRest1, FormatRest2, Index1, Index2). +format_args_get_index(FormatRest, FormatRest, none). + +% have at least one digit already. This is separate from format_args_get_index to distinguish {} and {0} cases +format_args_get_index1([C|FormatRest1], FormatRest2, Index1, Index3) :- char_code(C, Ccode), Ccode >= 48, Ccode =< 57, !, % in the range ['0'..'9'] + Index2 is (Index1*10)+(Ccode-48), + format_args_get_index1(FormatRest1, FormatRest2, Index2, Index3). +format_args_get_index1(FormatRest, FormatRest, Index, Index). + +% Placeholder to deal with formatting {:} later +format_args_get_format(FormatRest, FormatRest, _). + +format_args_write(Arg,_) :- string(Arg), !, write(Arg). +format_args_write('#\\'(Arg),_) :- !, write(Arg). +format_args_write(Arg,_) :- write_src_woi(Arg). + +format_args([], _, _). +format_args(['{','{'|FormatRest], Iterator, Args) :- !, put('{'), format_args(FormatRest, Iterator, Args). % escaped +format_args(['}','}'|FormatRest], Iterator, Args) :- !, put('}'), format_args(FormatRest, Iterator, Args). % escaped +format_args(['{'|FormatRest1], Iterator1, Args) :- + format_args_get_index(FormatRest1, FormatRest2, Index), + format_args_get_format(FormatRest2, ['}'|FormatRest3], Format), + % check that the closing '}' is not escaped with another '}' + ((FormatRest3=[] ; ((FormatRest3=[C|_],C\='}')) )), + % The Rust behaviour of advancing the iterator if an index is not specified + (((Index == none)) + -> ((nth0(Iterator1,Args,Arg),Iterator2 is Iterator1+1)) + ; ((nth0(Index,Args,Arg), Iterator2 is Iterator1))), + format_args_write(Arg,Format), + format_args(FormatRest3, Iterator2, Args). +format_args([C|FormatRest], Iterator, Args) :- put(C), format_args(FormatRest, Iterator, Args). + +eval_20(Eq,RetType,Depth,Self,['format-args',Format,Args],Result):- !, + eval_args(Eq,RetType,Depth,Self,Format,EFormat), + eval_args(Eq,RetType,Depth,Self,Args,EArgs), + string_chars(EFormat, FormatChars), user_io(with_output_to(string(Result), format_args(FormatChars, 0, EArgs))). +% string_chars(EFormat, FormatChars), wots(Result, format_args(FormatChars, 0, EArgs)). eval_20(Eq,RetType,_Depth,_Self,['flip'],Bool):- ignore(RetType='Bool'), !, as_tf(random(0,2,0),Bool), diff --git a/src/canary/metta_interp.pl b/src/canary/metta_interp.pl index 8e8e2fed4b3..c032736ed3b 100755 --- a/src/canary/metta_interp.pl +++ b/src/canary/metta_interp.pl @@ -612,6 +612,7 @@ % conversion between String and List of Chars 'stringToChars'(String, Chars) :- eval_H(['stringToChars', String], Chars). 'charsToString'(Chars, String) :- eval_H(['charsToString', Chars], String). +'format-args'(Format, Args, Result) :- eval_H(['format-args', Format, Args], Result). % ============================ % %%%% Random Utilities diff --git a/src/canary/metta_ontology.pfc.pl b/src/canary/metta_ontology.pfc.pl index 0c8f156ac30..d34575d5984 100755 --- a/src/canary/metta_ontology.pfc.pl +++ b/src/canary/metta_ontology.pfc.pl @@ -461,4 +461,5 @@ % --- String and Character manipulation --- properties('&corelib','stringToChars', [string_operations, qhelp("Convert a string to a list of chars."), string_to_chars]). properties('&corelib','charsToString', [string_operations, qhelp("Convert a list of chars to a string."), chars_to_string]). +properties('&corelib','format-args', [string_operations, qhelp("Generate a formatted string using a format specifier."), format_args]). properties('&corelib','flip', [random, qhelp("Return a random boolean."), random_boolean]). diff --git a/tests/baseline_compat/hyperon-mettalog_sanity/string-tests.metta b/tests/baseline_compat/hyperon-mettalog_sanity/string-tests.metta index 948d300d24a..9bbbbc24d78 100644 --- a/tests/baseline_compat/hyperon-mettalog_sanity/string-tests.metta +++ b/tests/baseline_compat/hyperon-mettalog_sanity/string-tests.metta @@ -1,3 +1,5 @@ +;; String <=> List of Characters + !(assertEqual (stringToChars "xyzzy") ('x' 'y' 'z' 'z' 'y')) !(assertEqual (charsToString ('x' 'y' 'z' 'z' 'y')) "xyzzy") @@ -21,3 +23,26 @@ !(assertEqual (stringToChars " ") (' ')) !(assertEqual (stringToChars (superpose ("ab" "cd" "ef"))) (superpose (('a' 'b') ('c' 'd') ('e' 'f')))) + +;; format-args + +!(assertEqual (format-args "" (1 2 3)) "") + +!(assertEqual (format-args " " (1 2 3)) " ") + +!(assertEqual (format-args "{}" (1 2 3)) "1") + +!(assertEqual (format-args "{}}" (1 2 3)) "{}") + +!(assertEqual (format-args "xyz zy" (1 2 3)) "xyz zy") + +!(assertEqual (format-args "Indexed {1} {} {0} {}" (1 2 3)) "Indexed 2 1 1 2") + +!(assertEqual (format-args "Different types {} {} {} {} {} {}" (1 "2" 'c' -0.5 atom (1 2 c -0.5 atom))) "Different types 1 2 c -0.5 atom (1 2 c -0.5 atom)") + +!(assertEqual (format-args "Two digit index={11}" (0 1 2 3 4 5 6 7 8 9 10 "eleven")) "Two digit index=eleven") + +;; malformed examples +!(assertEqual (format-args "Bad args list {1} {} {0} {}" x) "Bad args list {1} {} {0} {}") + +!(assertEqual (format-args "Malformed format}{{}{{{}{} {4} { } {-1} {x} {{{{{{}}}}}}{{{{{}}}}}" ("success1" "success2")) "Malformed format}{}{success1success2 {4} { } {-1} {x} {{{}}}{{{}}}") diff --git a/tests/baseline_compat/hyperon-mettalog_sanity/string-tests.metta.answers b/tests/baseline_compat/hyperon-mettalog_sanity/string-tests.metta.answers index 5d7ee096f32..c981c5b1dfc 100644 --- a/tests/baseline_compat/hyperon-mettalog_sanity/string-tests.metta.answers +++ b/tests/baseline_compat/hyperon-mettalog_sanity/string-tests.metta.answers @@ -9,3 +9,13 @@ [()] [()] [()] +[()] +[()] +[()] +[()] +[()] +[()] +[()] +[()] +[()] +[()]