Current section

Files

Jump to
ephp src ephp_lib_array.erl
Raw

src/ephp_lib_array.erl

%% @doc Array related functions. Here you can see documentation about the
%% implementation of the PHP functions for the section called <i>Array</i>.
-module(ephp_lib_array).
-author('manuel@altenwald.com').
-behaviour(ephp_lib).
-export([init_func/0, init_config/0, init_const/0, handle_error/3, in_array/5, count/3,
array_merge/3, list/3, array_unique/4, array_change_key_case/4, array_chunk/5,
array_column/5, array_pop/3, reset/3, current/3, php_end/3, prev/3, next/3, key/3,
ksort/4, array_keys/3, array_count_values/3]).
-include("ephp.hrl").
-include("ephp_array.hrl").
-define(CASE_LOWER, 0).
-define(CASE_UPPER, 1).
-spec init_func() -> ephp_lib:php_function_results().
init_func() ->
[{in_array, [{args, {2, 3, undefined, [mixed, array, {boolean, false}]}}]},
count,
{count, [{alias, <<"sizeof">>}]},
{array_merge, [pack_args]},
{list, [pack_args, {args, no_resolve}]},
{array_unique, [{args, {1, 2, undefined, [array, {integer, ?SORT_STRING}]}}]},
{array_change_key_case, [{args, {1, 2, undefined, [array, {integer, ?CASE_LOWER}]}}]},
{array_chunk, [{args, {2, 3, undefined, [array, integer, {boolean, false}]}}]},
{array_column, [{args, {2, 3, undefined, [array, mixed, {mixed, undefined}]}}]},
{reset, [{args, {1, 1, undefined, [array]}}]},
{current, [{args, {1, 1, undefined, [array]}}]},
{current, [{args, {1, 1, undefined, [array]}}, {alias, <<"pos">>}]},
{php_end, [{args, {1, 1, undefined, [array]}}, {alias, <<"end">>}]},
{prev, [{args, {1, 1, undefined, [array]}}]},
{next, [{args, {1, 1, undefined, [array]}}]},
{next, [{args, {1, 1, undefined, [array]}}, {alias, <<"each">>}]},
{key, [{args, {1, 1, undefined, [array]}}]},
{ksort, [{args, {1, 2, false, [array, {integer, ?SORT_REGULAR}]}}]},
{array_keys, [array]},
{array_pop, [array]},
{array_count_values, [{args, {1, 1, undefined, [array]}}]}].
-spec init_config() -> ephp_lib:php_config_results().
init_config() ->
[].
-spec init_const() -> ephp_lib:php_const_results().
%% @private
init_const() ->
[{<<"SORT_REGULAR">>, ?SORT_REGULAR},
{<<"SORT_NUMERIC">>, ?SORT_NUMERIC},
{<<"SORT_STRING">>, ?SORT_STRING},
{<<"SORT_LOCALE_STRING">>, ?SORT_LOCALE_STRING},
{<<"CASE_LOWER">>, ?CASE_LOWER},
{<<"CASE_UPPER">>, ?CASE_UPPER}].
-spec handle_error(ephp_error:error_type(), ephp_error:error_level(), Args :: term()) ->
string() | ignore.
%% @private
handle_error(enooffset, _Level, {Offset}) ->
io_lib:format("Undefined offset: ~p", [Offset]);
handle_error(ecountvalues, _Level, _Extra) ->
"array_count_values(): Can only count STRING and INTEGER values!";
handle_error(_Type, _Level, _Data) ->
ignore.
-spec in_array(ephp:context_id(),
line(),
Key :: var_value(),
Array :: var_value(),
Strict :: var_value()) ->
boolean().
in_array(_Context, _Line, {_, Value}, {_, Array}, {_, Strict}) ->
member(Value, Array, ephp_data:to_bool(Strict)).
-spec count(ephp:context_id(), line(), Array :: var_value()) -> integer().
count(_Context, _Line, {_, Array}) when ?IS_ARRAY(Array) ->
ephp_array:size(Array);
count(_Context, _Line, _Var) ->
1.
-spec array_merge(ephp:context_id(), line(), Arrays :: [var_value()]) ->
ephp_array:ephp_array().
array_merge(Context, Line, Args) ->
array_merge(Context, Line, 1, Args).
-spec list(ephp:context_id(), line(), Vars :: [var_value()]) ->
ephp_array:ephp_array() | undefined.
list(Context, Line, [{Array, undefined} | Getters]) when ?IS_ARRAY(Array) ->
lists:foldl(fun ({[], _}, I) ->
I + 1;
({Getter, _}, I) ->
case ephp_array:find(I, Array) of
{ok, Value} ->
ephp_context:set(Context, Getter, Value);
error ->
File = ephp_context:get_active_file(Context),
Error = {error, enooffset, Line, File, ?E_NOTICE, {I}},
ephp_error:handle_error(Context, Error),
ephp_context:set(Context, Getter, undefined)
end,
I + 1
end,
0,
Getters),
Array;
list(Context, _Line, [{Binary, undefined} | Getters]) when is_binary(Binary) ->
lists:foreach(fun ({[], _}) ->
ok;
({G, _}) ->
ephp_context:set(Context, G, undefined)
end,
Getters),
undefined.
-spec array_unique(ephp:context_id(),
line(),
Array :: var_value(),
Flags :: var_value()) ->
ephp_array:ephp_array().
array_unique(_Context, _Line, {_, Array}, {_, Flags}) ->
%% TODO error when Flags is not a SORT_* valid value
ephp_array:from_list(unique(ephp_array:to_list(Array), [], Flags)).
-spec array_change_key_case(ephp:context_id(),
line(),
Array :: var_value(),
Flags :: var_value()) ->
ephp_array:ephp_array().
array_change_key_case(_Context, _Line, {_, Array}, {_, Flags}) ->
ephp_array:map(fun(K, V) ->
if Flags >= 1 ->
{ephp_string:to_upper(K), V};
true ->
{ephp_string:to_lower(K), V}
end
end,
Array).
-spec array_chunk(ephp:context_id(),
line(),
Array :: var_value(),
Size :: var_value(),
PreserveKeys :: var_value()) ->
ephp_array:ephp_array().
array_chunk(_Context, _Line, {_, Array}, {_, Size}, {_, PreserveKeys}) ->
case ephp_array:size(Array) > Size of
false ->
Array;
true ->
ephp_array:from_list(chunk(ephp_array:to_list(Array), Size, PreserveKeys))
end.
-spec array_column(ephp:context_id(), line(), var_value(), var_value(), var_value()) ->
false | ephp_array:ephp_array().
%% @doc in an array of arrays it retries the subelements with the key passed
%% as a param. It let you to change the new key to retrieve the elements.
%% @end
array_column(Context, Line, _Array, {_, ColKey}, _IdxKey)
when not (is_binary(ColKey) orelse is_number(ColKey)) ->
File = ephp_context:get_active_file(Context),
Error =
{error,
eshouldbe,
Line,
File,
?E_WARNING,
{<<"array_column">>, <<"The column key">>, <<"either a string or an integer">>}},
ephp_error:handle_error(Context, Error),
false;
array_column(Context, Line, _Array, _ColKey, {_, IdxKey})
when not (is_binary(IdxKey) orelse is_number(IdxKey) orelse IdxKey =:= undefined) ->
File = ephp_context:get_active_file(Context),
Error =
{error,
eshouldbe,
Line,
File,
?E_WARNING,
{<<"array_column">>, <<"The index key">>, <<"either a string or an integer">>}},
ephp_error:handle_error(Context, Error),
false;
array_column(_Context, _Line, {_, Array}, {_, ColKey}, {_, IdxKey}) ->
ephp_array:fold(fun(_, V, A) ->
case ephp_array:find(ColKey, V) of
{ok, Val} ->
Key = case IdxKey of
undefined ->
auto;
_ ->
ephp_array:find(IdxKey, V, auto)
end,
ephp_array:store(Key, Val, A);
error ->
A
end
end,
ephp_array:new(),
Array).
-spec reset(ephp:context_id(), line(), Array :: var_value()) ->
false | ephp_array:ephp_array().
%% @doc resets the cursor for an array moving it to the first element and
%% retrieving that element if it's exists, false otherwise.
%% @end
reset(Context, _Line, {Var, Array}) ->
case ephp_array:first(Array) of
{error, _} ->
false;
{ok, {_Key, Value}, NewArray} ->
ephp_context:set(Context, Var, NewArray),
Value
end.
-spec current(ephp:context_id(), line(), Array :: var_value()) ->
false | ephp_array:ephp_array().
%% @doc retrieve the current element under the cursor for an array.
current(_Context, _Line, {_, Array}) ->
case ephp_array:current(Array) of
{error, _} ->
false;
{ok, {_Key, Value}} ->
Value
end.
-spec php_end(ephp:context_id(), line(), Array :: var_value()) ->
false | ephp_array:ephp_array().
%% @doc moves the array cursor to the last element and retrieves it.
php_end(Context, _Line, {Var, Array}) ->
case ephp_array:last(Array) of
{error, _} ->
false;
{ok, {_Key, Value}, NewArray} ->
ephp_context:set(Context, Var, NewArray),
Value
end.
-spec prev(ephp:context_id(), line(), Array :: var_value()) ->
false | ephp_array:ephp_array().
%% @doc moves the cursor to the previous element and retrieves it if it's
%% possible, false otherwise.
%% @end
prev(Context, _Line, {Var, Array}) ->
case ephp_array:prev(Array) of
{error, bof} ->
ephp_context:set(Context, Var, ephp_array:cursor(Array, false)),
false;
{error, _} ->
false;
{ok, {_Key, Value}, NewArray} ->
ephp_context:set(Context, Var, NewArray),
Value
end.
-spec next(ephp:context_id(), line(), Array :: var_value()) -> false | mixed().
%% @doc moves the cursor to the next element and retrieves it if it's possible,
%% false otherwise.
%% @end
next(Context, _Line, {Var, Array}) ->
case ephp_array:next(Array) of
{error, eof} ->
ephp_context:set(Context, Var, ephp_array:cursor(Array, false)),
false;
{error, _} ->
false;
{ok, {_Key, Value}, NewArray} ->
ephp_context:set(Context, Var, NewArray),
Value
end.
-spec key(ephp:context_id(), line(), Array :: var_value()) -> undefined | mixed().
%% @doc returns the key under the cursor or undefined if an error happens.
key(_Context, _Line, {_, Array}) ->
case ephp_array:current(Array) of
{error, _} ->
undefined;
{ok, {Key, _Value}} ->
Key
end.
-spec ksort(ephp:context_id(), line(), Array :: var_value(), SortType :: var_value()) ->
boolean().
%% @doc order the array based on the keys modifying the original. The function
%% returns true if the ordering was ok, otherwise false. We can use different
%% sort types: SORT_REGULAR (default), SORT_NUMERIC, SORT_STRING,
%% SORT_LOCALE_STRING, SORT_NATURAL, SORT_FLAG_CASE.
ksort(Context, _Line, {ArrayVar, Array}, {_, SortType}) ->
ephp_context:set(Context, ArrayVar, ephp_array:ksort(Array, SortType)),
true.
-spec array_keys(ephp:context_id(), line(), Array :: var_value()) ->
ephp_array:ephp_array().
%% @doc returns a new array with the keys.
array_keys(_Context, _Line, {_, Array}) ->
ephp_array:keys(Array).
-spec array_pop(ephp:context_id(), line(), Array :: var_value()) ->
ephp_array:ephp_array().
%% @doc returns the last element of the array and removes it from the array.
array_pop(Context, _Line, {VarArray, Array}) ->
{Head, TailArray} = ephp_array:pop(Array),
ephp_context:set(Context, VarArray, TailArray),
Head.
array_count_values(Context, Line, {_, Values}) ->
Result = ephp_array:fold(fun(_Key, Value, Acc) ->
if
is_integer(Value) orelse is_binary(Value) ->
maps:put(Value, maps:get(Value, Acc, 0) + 1, Acc);
true ->
File = ephp_context:get_active_file(Context),
Error =
{error,
ecountvalues,
Line,
File,
?E_WARNING,
undefined},
ephp_error:handle_error(Context, Error),
Acc
end
end, #{}, Values),
ephp_array:from_map(Result).
%% ----------------------------------------------------------------------------
%% Internal functions
%% ----------------------------------------------------------------------------
chunk(Array, N, true) when length(Array) =< N ->
[ephp_array:from_list(Array)];
chunk(Array, N, false) when length(Array) =< N ->
{_, NewArray} =
lists:foldl(fun({_, V}, {K, Res}) -> {K + 1, Res ++ [{K, V}]} end, {0, []}, Array),
[ephp_array:from_list(NewArray)];
chunk(Array, N, PrKeys) ->
{A1, A2} = lists:split(N, Array),
chunk(A1, N, PrKeys) ++ chunk(A2, N, PrKeys).
unique([], Array, _Flags) ->
Array;
unique([{Key, Val} | Rest], Array, ?SORT_REGULAR) ->
Check = fun({_, V}) -> ephp_data:is_equal(V, Val) end,
case lists:any(Check, Array) of
true ->
unique(Rest, Array, ?SORT_REGULAR);
false ->
unique(Rest, Array ++ [{Key, Val}], ?SORT_REGULAR)
end;
unique([{Key, Val} | Rest], Array, ?SORT_NUMERIC) ->
NumArray = [{K, ephp_data:to_number(V)} || {K, V} <- Array],
NumVal = ephp_data:to_number(Val),
Check = fun({_, V}) -> V == NumVal end,
case lists:any(Check, NumArray) of
true ->
unique(Rest, Array, ?SORT_NUMERIC);
false ->
unique(Rest, Array ++ [{Key, Val}], ?SORT_NUMERIC)
end;
unique([{Key, Val} | Rest], Array, _Flags) ->
StrArray = [{K, ephp_data:to_bin(V)} || {K, V} <- Array],
StrVal = ephp_data:to_bin(Val),
case lists:keyfind(StrVal, 2, StrArray) of
{_, StrVal} ->
unique(Rest, Array, ?SORT_STRING);
false ->
unique(Rest, Array ++ [{Key, Val}], ?SORT_STRING)
end.
%% TODO: SORT_LOCALE_STRING
member(Value, Dict, true) ->
List = ephp_array:to_list(Dict),
lists:keysearch(Value, 2, List) =/= false;
member(Value, Dict, false) ->
List = ephp_array:to_list(Dict),
lists:any(fun({_, Member}) -> ephp_data:is_equal(Member, Value) end, List).
-spec array_merge(ephp:context_id(), line(), pos_integer(), Arrays :: [var_value()]) ->
ephp_array:ephp_array().
array_merge(Context, Line, 1, [{_, Array} | _]) when not ?IS_ARRAY(Array) ->
Data = {<<"array_merge">>, 1},
File = ephp_context:get_active_file(Context),
Error = {error, eargtype, Line, File, ?E_WARNING, Data},
ephp_error:handle_error(Context, Error),
undefined;
array_merge(Context, Line, N, [{_, _}, {_, V} | _]) when not ?IS_ARRAY(V) ->
Data = {<<"array_merge">>, N + 1},
File = ephp_context:get_active_file(Context),
Error = {error, eargtype, Line, File, ?E_WARNING, Data},
ephp_error:handle_error(Context, Error),
undefined;
array_merge(_Context, _Line, _N, [{_, Array}]) ->
Array;
array_merge(Context, Line, N, [{V1, A1}, {_, A2} | Rest]) when ?IS_ARRAY(A2) ->
Array =
lists:foldl(fun ({K, V}, A) when is_integer(K) ->
ephp_array:store(auto, V, A);
({K, V}, A) ->
ephp_array:store(K, V, A)
end,
A1,
ephp_array:to_list(A2)),
array_merge(Context, Line, N + 1, [{V1, Array} | Rest]).