From a9d0d119837fb0bc52d2bb3d48a47568de9100b4 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Tue, 28 Feb 2017 15:23:11 +0100 Subject: Handle chardata in string:to_float and string:to_list --- erts/emulator/beam/bif.c | 4 +- erts/emulator/beam/bif.tab | 4 +- erts/emulator/test/list_bif_SUITE.erl | 4 +- lib/stdlib/src/string.erl | 71 +++++++++++++++++++++++++---- lib/stdlib/test/string_SUITE.erl | 84 +++++++++++++++++++++++++++-------- 5 files changed, 134 insertions(+), 33 deletions(-) diff --git a/erts/emulator/beam/bif.c b/erts/emulator/beam/bif.c index 214de3652f..075909a881 100644 --- a/erts/emulator/beam/bif.c +++ b/erts/emulator/beam/bif.c @@ -3109,7 +3109,7 @@ BIF_RETTYPE integer_to_list_1(BIF_ALIST_1) * On error returns: {error,not_a_list}, or {error, no_integer} */ -BIF_RETTYPE string_to_integer_1(BIF_ALIST_1) +BIF_RETTYPE string_list_to_integer_1(BIF_ALIST_1) { Eterm res; Eterm tail; @@ -3295,7 +3295,7 @@ BIF_RETTYPE float_to_binary_2(BIF_ALIST_2) #define LOAD_E(xi,xim,xl,xlm) ((xi)=(xim), (xl)=(xlm)) #define STRING_TO_FLOAT_BUF_INC_SZ (128) -BIF_RETTYPE string_to_float_1(BIF_ALIST_1) +BIF_RETTYPE string_list_to_float_1(BIF_ALIST_1) { Eterm orig = BIF_ARG_1; Eterm list = orig; diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab index 66e5dc2988..5aee795088 100644 --- a/erts/emulator/beam/bif.tab +++ b/erts/emulator/beam/bif.tab @@ -460,8 +460,8 @@ bif error_logger:warning_map/0 bif erlang:get_module_info/1 bif erlang:get_module_info/2 ubif erlang:is_boolean/1 -bif string:to_integer/1 -bif string:to_float/1 +bif string:list_to_integer/1 +bif string:list_to_float/1 bif erlang:make_fun/3 bif erlang:iolist_size/1 bif erlang:iolist_to_binary/1 diff --git a/erts/emulator/test/list_bif_SUITE.erl b/erts/emulator/test/list_bif_SUITE.erl index 514dd2f412..9c40929926 100644 --- a/erts/emulator/test/list_bif_SUITE.erl +++ b/erts/emulator/test/list_bif_SUITE.erl @@ -47,9 +47,9 @@ t_list_to_integer(Config) when is_list(Config) -> {12373281903728109372810937209817320981321,"ABC"} = string:to_integer("12373281903728109372810937209817320981321ABC"), {-12373281903728109372810937209817320981321,"ABC"} = string:to_integer("-12373281903728109372810937209817320981321ABC"), {12,[345]} = string:to_integer([$1,$2,345]), - {12,[a]} = string:to_integer([$1,$2,a]), + {error, badarg} = string:to_integer([$1,$2,a]), {error,no_integer} = string:to_integer([$A]), - {error,not_a_list} = string:to_integer($A), + {error,badarg} = string:to_integer($A), ok. %% Test hd/1 with correct and incorrect arguments. diff --git a/lib/stdlib/src/string.erl b/lib/stdlib/src/string.erl index 4fdfe99b66..a0fa60fbc7 100644 --- a/lib/stdlib/src/string.erl +++ b/lib/stdlib/src/string.erl @@ -61,6 +61,8 @@ next_codepoint/1, next_grapheme/1 ]). +-export([to_float/1, to_integer/1]). + %% Old (will be deprecated) lists/string API kept for backwards compability -export([len/1, concat/2, % equal/2, (extended in the new api) chr/2,rchr/2,str/2,rstr/2, @@ -80,26 +82,28 @@ -type grapheme_cluster() :: char() | [char()]. -type direction() :: 'leading' | 'trailing'. -%%% BIFs --export([to_float/1, to_integer/1]). -dialyzer({no_improper_lists, stack/2}). +%%% BIFs internal (not documented) should not to be used outside of this module +%%% May be removed +-export([list_to_float/1, list_to_integer/1]). --spec to_float(String) -> {Float, Rest} | {error, Reason} when +%% Uses bifs: string:list_to_float/1 and string:list_to_integer/1 +-spec list_to_float(String) -> {Float, Rest} | {'error', Reason} when String :: string(), Float :: float(), Rest :: string(), - Reason :: no_float | not_a_list. + Reason :: 'no_float' | 'not_a_list'. -to_float(_) -> +list_to_float(_) -> erlang:nif_error(undef). --spec to_integer(String) -> {Int, Rest} | {error, Reason} when +-spec list_to_integer(String) -> {Int, Rest} | {'error', Reason} when String :: string(), Int :: integer(), Rest :: string(), - Reason :: no_integer | not_a_list. + Reason :: 'no_integer' | 'not_a_list'. -to_integer(_) -> +list_to_integer(_) -> erlang:nif_error(undef). %%% End of BIFs @@ -335,6 +339,57 @@ casefold(CD) when is_list(CD) -> casefold(CD) when is_binary(CD) -> casefold_bin(CD,<<>>). +-spec to_integer(String) -> {Int, Rest} | {'error', Reason} when + String :: unicode:chardata(), + Int :: integer(), + Rest :: unicode:chardata(), + Reason :: 'no_integer' | badarg. + +to_integer(String) -> + try take(String, "+-0123456789") of + {Head, Tail} -> + case is_empty(Head) of + true -> {error, no_integer}; + false -> + List = unicode:characters_to_list(Head), + case string:list_to_integer(List) of + {error, _} = Err -> Err; + {Int, Rest} -> + to_number(String, Int, Rest, List, Tail) + end + end + catch _:_ -> {error, badarg} + end. + +-spec to_float(String) -> {Float, Rest} | {'error', Reason} when + String :: unicode:chardata(), + Float :: float(), + Rest :: unicode:chardata(), + Reason :: 'no_float' | 'badarg'. + +to_float(String) -> + try take(String, "+-0123456789eE.,") of + {Head, Tail} -> + case is_empty(Head) of + true -> {error, no_float}; + false -> + List = unicode:characters_to_list(Head), + case string:list_to_float(List) of + {error, _} = Err -> Err; + {Float, Rest} -> + to_number(String, Float, Rest, List, Tail) + end + end + catch _:_ -> {error, badarg} + end. + +to_number(String, Number, Rest, List, _Tail) when is_binary(String) -> + BSz = length(List)-length(Rest), + <<_:BSz/binary, Cont/binary>> = String, + {Number, Cont}; +to_number(_, Number, Rest, _, Tail) -> + {Number, concat(Rest,Tail)}. + %% Return the remaining string with prefix removed or else nomatch -spec prefix(String::unicode:chardata(), Prefix::unicode:chardata()) -> 'nomatch' | unicode:chardata(). diff --git a/lib/stdlib/test/string_SUITE.erl b/lib/stdlib/test/string_SUITE.erl index a78ddf761b..fe00b7ff91 100644 --- a/lib/stdlib/test/string_SUITE.erl +++ b/lib/stdlib/test/string_SUITE.erl @@ -34,6 +34,7 @@ equal/1, pad/1, trim/1, chomp/1, take/1, uppercase/1, lowercase/1, titlecase/1, casefold/1, + to_integer/1,to_float/1, prefix/1, split/1, replace/1, find/1, lexemes/1, nth_lexeme/1, cd_gc/1, meas/1 ]). @@ -42,7 +43,7 @@ -export([span_cspan/1,substr/1,old_tokens/1,chars/1]). -export([copies/1,words/1,strip/1,sub_word/1,left_right/1]). -export([sub_string/1,centre/1, join/1]). --export([to_integer/1,to_float/1]). +-export([old_to_integer/1,old_to_float/1]). -export([to_upper_to_lower/1]). %% Run tests when debugging them @@ -61,14 +62,15 @@ groups() -> equal, reverse, slice, pad, trim, chomp, take, lexemes, nth_lexeme, + to_integer, to_float, uppercase, lowercase, titlecase, casefold, prefix, find, split, replace, cd_gc, meas]}, {list_string, [len, old_equal, old_concat, chr_rchr, str_rstr, span_cspan, substr, old_tokens, chars, copies, words, strip, sub_word, - left_right, sub_string, centre, join, to_integer, - to_float, to_upper_to_lower]}]. + left_right, sub_string, centre, join, old_to_integer, + old_to_float, to_upper_to_lower]}]. init_per_suite(Config) -> Config. @@ -456,6 +458,31 @@ casefold(_) -> ?TEST("İ I WITH DOT ABOVE", [], "i̇ i with dot above"), ok. + +to_integer(_) -> + ?TEST("", [], {error, no_integer}), + ?TEST("-", [], {error, no_integer}), + ?TEST("01", [], {1, ""}), + ?TEST("1.53", [], {1, ".53"}), + ?TEST("+01.53", [], {1, ".53"}), + ?TEST("-1.53", [], {-1, ".53"}), + ?TEST("-13#16FF", [], {-13, "#16FF"}), + ?TEST("13xFF", [], {13, "xFF"}), + ?TEST(["234", <<"3+4-234">>], [], {2343, "+4-234"}), + ok. + +to_float(_) -> + ?TEST("", [], {error, no_float}), + ?TEST("1.53", [], {1.53, ""}), + ?TEST("+01.53foo", [], {1.53, "foo"}), + ?TEST("-1.53foo", [], {-1.53, "foo"}), + ?TEST("-1,53foo", [], {-1.53, "foo"}), + ?TEST("-1,53e1foo", [], {-15.3, "foo"}), + ?TEST("-1,53e-1", [], {-0.153, ""}), + ?TEST("-1,53E-1+2", [], {-0.153, "+2"}), + ?TEST(["-1,53", <<"E-1+2">>], [], {-0.153, "+2"}), + ok. + prefix(_) -> ?TEST("", ["a"], nomatch), ?TEST("a", [""], "a"), @@ -848,6 +875,8 @@ check_types_1({list, deep}, _) -> ok; check_types_1({list, {list, deep}}, _) -> ok; +check_types_1(_, {error,_}) -> + ok; check_types_1(T1,T2) -> {T1,T2}. @@ -876,6 +905,11 @@ type(List) when is_list(List) -> false -> mixed end end; +type({Number, String}) when is_number(Number) -> + %% to_integer or to_float + type(String); +type({Atom, _}=What) when is_atom(Atom) -> + What; type({R1,R2}) -> case {type(R1),type(R2)} of {T,T} -> T; @@ -1192,7 +1226,7 @@ centre(Config) when is_list(Config) -> {'EXIT',_} = (catch string:centre(hello, 10)), ok. -to_integer(Config) when is_list(Config) -> +old_to_integer(Config) when is_list(Config) -> {1,""} = test_to_integer("1"), {1,""} = test_to_integer("+1"), {-1,""} = test_to_integer("-1"), @@ -1205,9 +1239,10 @@ to_integer(Config) when is_list(Config) -> {error,no_integer} = test_to_integer(""), {error,no_integer} = test_to_integer("!1"), {error,no_integer} = test_to_integer("F1"), - {error,not_a_list} = test_to_integer('23'), - {3,[[]]} = test_to_integer([$3,[]]), - {3,[hello]} = test_to_integer([$3,hello]), + {error,badarg} = test_to_integer('23'), + %% {3,[[]]} = test_to_integer([$3,[]]), + %% {3,[hello]} = test_to_integer([$3,hello]), + {error,badarg} = test_to_integer([$3,hello]), ok. test_to_integer(Str) -> @@ -1221,7 +1256,7 @@ test_to_integer(Str) -> Res end. -to_float(Config) when is_list(Config) -> +old_to_float(Config) when is_list(Config) -> {1.2,""} = test_to_float("1.2"), {1.2,""} = test_to_float("1,2"), {120.0,""} = test_to_float("1.2e2"), @@ -1248,9 +1283,11 @@ to_float(Config) when is_list(Config) -> {error,no_float} = test_to_float("1"), {error,no_float} = test_to_float("1e"), {error,no_float} = test_to_float("2."), - {error,not_a_list} = test_to_float('2.3'), - {2.3,[[]]} = test_to_float([$2,$.,$3,[]]), - {2.3,[hello]} = test_to_float([$2,$.,$3,hello]), + {error,badarg} = test_to_float('2.3'), + %{2.3,[[]]} = test_to_float([$2,$.,$3,[]]), + {2.3,[]} = test_to_float([$2,$.,$3,[]]), + %%{2.3,[hello]} = test_to_float([$2,$.,$3,hello]), + {error, badarg} = test_to_float([$2,$.,$3,hello]), ok. test_to_float(Str) -> @@ -1265,23 +1302,32 @@ test_to_float(Str) -> end. to_upper_to_lower(Config) when is_list(Config) -> - "1234ABCDEFÅÄÖ=" = string:to_upper("1234abcdefåäö="), - "éèíúùòóåäöabc()" = string:to_lower("ÉÈÍÚÙÒÓÅÄÖabc()"), + "1234ABCDEFÅÄÖ=" = string_to_upper("1234abcdefåäö="), + "éèíúùòóåäöabc()" = string_to_lower("ÉÈÍÚÙÒÓÅÄÖabc()"), All = lists:seq(0, 255), - UC = string:to_upper(All), + UC = string_to_upper(All), 256 = erlang:length(UC), all_upper_latin1(UC, 0), - LC = string:to_lower(All), + LC = string_to_lower(All), all_lower_latin1(LC, 0), - LC = string:to_lower(string:to_upper(LC)), - LC = string:to_lower(string:to_upper(UC)), - UC = string:to_upper(string:to_lower(LC)), - UC = string:to_upper(string:to_lower(UC)), + LC = string_to_lower(string_to_upper(LC)), + LC = string_to_lower(string_to_upper(UC)), + UC = string_to_upper(string_to_lower(LC)), + UC = string_to_upper(string_to_lower(UC)), + ok. +string_to_lower(Str) -> + Res = string:to_lower(Str), + Res = [string:to_lower(C) || C <- Str]. + +string_to_upper(Str) -> + Res = string:to_upper(Str), + Res = [string:to_upper(C) || C <- Str]. + all_upper_latin1([C|T], C) when 0 =< C, C < $a; $z < C, C < 16#E0; C =:= 16#F7; C =:= 16#FF -> -- cgit v1.2.3