-module(ephp_data). -author('manuel@altenwald.com'). -include("ephp.hrl"). -include("ephp_array.hrl"). -export([gettype/1, is_equal/2, to_bin/1, to_bin/3, to_int/1, to_int/3, to_float/1, to_float/3, to_number/1, to_number/3, to_boolean/1, bin_to_number/1, bin_to_number/2, increment_code/1, to_bool/1, zero_if_undef/1, pad_to_bin/2, ceiling/1, flooring/1, urand/0, pow/2, instance_of/3]). -spec gettype(mixed()) -> binary(). gettype(Value) when is_boolean(Value) -> <<"boolean">>; gettype(Value) when is_integer(Value) -> <<"integer">>; gettype(Value) when is_float(Value) -> <<"float">>; gettype(Value) when is_binary(Value) -> <<"string">>; gettype(Value) when ?IS_ARRAY(Value) -> <<"array">>; gettype(Value) when ?IS_OBJECT(Value) -> <<"object">>; gettype(Value) when ?IS_RESOURCE(Value) -> <<"resource">>; gettype(Value) when ?IS_FUNCTION(Value) -> <<"object">>; gettype(undefined) -> <<"NULL">>; gettype(infinity) -> <<"float">>; gettype(nan) -> <<"float">>. -spec is_equal(A :: mixed(), B :: mixed()) -> boolean(). is_equal(A, B) -> At = gettype(A), Bt = gettype(B), case At =:= Bt of true -> A == B; false -> case {At, Bt} of {<<"integer">>, <<"string">>} -> A == bin_to_number(B); {<<"string">>, <<"integer">>} -> bin_to_number(A) == B; {<<"float">>, <<"string">>} -> A == bin_to_number(B); {<<"string">>, <<"float">>} -> bin_to_number(A) == B; {<<"boolean">>, _} -> A == to_bool(B); {_, <<"boolean">>} -> to_bool(A) == B; _ -> A == B end end. -spec bin_to_number(binary(), Force :: true) -> integer() | float(); (binary(), Force :: false) -> integer() | float() | undefined. bin_to_number(Bin, Force) -> RE = "^([+-])?((\\.)[0-9]+|[0-9]+(\\.)[0-9]+|[0-9]+)(e[+-]?[1-9][0-9]*)?", {ok, R} = re:compile(RE), case re:run(Bin, R, [{capture, all, binary}]) of nomatch when Force -> 0; nomatch -> undefined; {match, [_, Sign, Num]} -> binary_to_integer(<>); {match, [_, Sign, Num, P1, P2]} when P1 =:= <<".">> orelse P2 =:= <<".">> -> binary_to_float(<>); {match, [_, Sign, Num, <<>>, <<>>, Exp]} -> binary_to_float(<>); {match, [_, Sign, Num, _P1, _P2, Exp]} -> binary_to_float(<>) end. -spec bin_to_number(binary()) -> integer() | float(). bin_to_number(Bin) when is_binary(Bin) -> bin_to_number(Bin, true). -spec to_int(A :: mixed()) -> integer(). to_int(A) when is_binary(A) -> bin_to_number(A); to_int(A) when is_integer(A) -> A; to_int(A) when is_float(A) -> flooring(A); to_int(A) when ?IS_ARRAY(A) -> case ephp_array:size(A) of 0 -> 0; _ -> 1 end; to_int(true) -> 1; % FIXME: conversion from NAN or INF to int is a bit tricky... to_int(infinity) -> ?PHP_INT_MIN; to_int(nan) -> ?PHP_INT_MIN; to_int(_) -> 0. -spec to_int(ephp:context_id(), line(), mixed()) -> integer(). to_int(Ctx, Line, #obj_ref{pid = Objects, ref = ObjectId}) -> ClassName = ephp_object:get_class_name(Objects, ObjectId), File = ephp_context:get_active_file(Ctx), Data = {ClassName, <<"int">>}, ephp_error:handle_error(Ctx, {error, enocast, Line, File, ?E_NOTICE, Data}), 1; to_int(_Ctx, _Line, Val) -> to_int(Val). -spec to_float(mixed()) -> float(). to_float(I) when is_integer(I) -> erlang:float(I); to_float(T) when is_binary(T) -> erlang:float( ephp_data:bin_to_number(T)); to_float(F) when is_float(F) -> F; to_float(A) when ?IS_ARRAY(A) -> case ephp_array:size(A) of 0 -> 0.0; _ -> 1.0 end; to_float(true) -> 1.0; to_float(false) -> 0.0; to_float(infinity) -> infinity; to_float(nan) -> nan; to_float(undefined) -> 0.0. -spec to_float(ephp:context_id(), line(), mixed()) -> float(). to_float(Ctx, Line, #obj_ref{pid = Objects, ref = ObjectId}) -> ClassName = ephp_object:get_class_name(Objects, ObjectId), File = ephp_context:get_active_file(Ctx), Data = {ClassName, <<"double">>}, ephp_error:handle_error(Ctx, {error, enocast, Line, File, ?E_NOTICE, Data}), 1.0; to_float(_Context, _Line, Value) -> to_float(Value). -spec to_number(mixed()) -> float() | integer(). to_number(I) when is_integer(I) -> I; to_number(T) when is_binary(T) -> ephp_data:bin_to_number(T); to_number(F) when is_float(F) -> F; to_number(A) when ?IS_ARRAY(A) -> case ephp_array:size(A) of 0 -> 0; _ -> 1 end; to_number(true) -> 1; to_number(false) -> 0; to_number(infinity) -> infinity; to_number(nan) -> nan; to_number(undefined) -> 0. -spec to_number(ephp:context_id(), line(), mixed()) -> float() | integer(). to_number(Ctx, Line, #obj_ref{pid = Objects, ref = ObjectId}) -> ClassName = ephp_object:get_class_name(Objects, ObjectId), File = ephp_context:get_active_file(Ctx), Data = {ClassName, <<"integer">>}, ephp_error:handle_error(Ctx, {error, enocast, Line, File, ?E_NOTICE, Data}), 1; to_number(_Context, _Line, Value) -> to_number(Value). -spec to_bin(A :: mixed()) -> binary(). to_bin(A) when is_binary(A) -> A; to_bin(A) when ?IS_ARRAY(A) -> <<"Array">>; to_bin(A) when is_list(A) -> list_to_binary(A); to_bin(A) when is_integer(A) -> integer_to_binary(A); to_bin(A) when is_float(A) -> Precision = ephp_config:get(<<"precision">>), case flooring(A) of F when A - F == 0 -> integer_to_binary(F); _ -> float_to_binary(A, [{decimals, Precision}, compact]) end; to_bin(true) -> <<"1">>; to_bin(false) -> <<>>; to_bin(nan) -> <<"NAN">>; to_bin(infinity) -> <<"INF">>; to_bin(undefined) -> <<>>; to_bin(MemRef) when ?IS_MEM(MemRef) -> to_bin(ephp_mem:get(MemRef)). -spec to_bin(ephp:context_id(), line(), mixed()) -> binary(). to_bin(Ctx, Line, Array) when ?IS_ARRAY(Array) -> File = ephp_context:get_active_file(Ctx), Data = {<<"string">>}, Error = {error, earrayconv, Line, File, ?E_NOTICE, Data}, ephp_error:handle_error(Ctx, Error), <<"Array">>; to_bin(Context, Line, ObjRef) when ?IS_OBJECT(ObjRef) -> #ephp_object{class = #class{name = ClassName}} = ephp_object:get(ObjRef), try Call = #call{name = <<"__toString">>, line = Line, type = object}, ephp_context:call_method(Context, ObjRef, Call) catch {error, eundefmethod, _, _, {ClassName, <<"__toString">>}} -> Data = {ClassName}, Error = {error, enotostring, Line, ?E_RECOVERABLE_ERROR, Data}, ephp_error:error(Error) end; to_bin(Context, Line, MemRef) when ?IS_MEM(MemRef) -> to_bin(Context, Line, ephp_mem:get(MemRef)); to_bin(_Context, _Line, Val) -> to_bin(Val). -spec to_boolean(mixed()) -> boolean(). to_boolean(0) -> false; to_boolean(N) when N == +0.00 orelse N == -0.00 -> false; to_boolean(N) when is_number(N) -> true; to_boolean(<<>>) -> false; to_boolean(S) when is_binary(S) -> true; to_boolean(true) -> true; to_boolean(false) -> false; to_boolean(infinity) -> true; to_boolean(nan) -> true; to_boolean(undefined) -> false; to_boolean(Array) when ?IS_ARRAY(Array) -> case ephp_array:size(Array) of 0 -> false; _ -> true end; to_boolean(#obj_ref{}) -> true. -spec increment_code(Code :: binary()) -> integer() | binary(). increment_code(<<>>) -> 1; increment_code(Code) when is_binary(Code) -> S = byte_size(Code) - 1, <> = Code, if T >= $a andalso T < $z orelse T >= $A andalso T < $Z orelse T >= $0 andalso T < $9 -> <>; T =:= $z andalso H =/= <<>> -> NewH = increment_code(H), <>; T =:= $z -> <<"aa">>; T =:= $Z andalso H =/= <<>> -> NewH = increment_code(H), <>; T =:= $Z -> <<"AA">>; T =:= $9 andalso H =/= <<>> -> NewH = increment_code(H), <>; T =:= $9 -> <<"10">>; true -> <> end. -spec to_bool(Value :: undefined | boolean() | ephp_array:ephp_array() | integer() | float() | string() | binary()) -> boolean(). to_bool(false) -> false; to_bool(undefined) -> false; to_bool(0) -> false; to_bool(<<>>) -> false; to_bool([]) -> false; to_bool(Array) when ?IS_ARRAY(Array) -> ephp_array:new() =/= Array; to_bool(_Other) -> true. -spec zero_if_undef(Value :: undefined | ephp_array:ephp_array() | integer() | float() | string() | binary()) -> integer() | infinity | nan | float(). zero_if_undef(undefined) -> 0; zero_if_undef(Value) when ?IS_ARRAY(Value) -> throw(einvalidop); zero_if_undef(infinity) -> infinity; zero_if_undef(nan) -> nan; zero_if_undef(Value) when not is_number(Value) -> 0; zero_if_undef(Value) -> Value. -spec pad_to_bin(Num :: integer() | binary(), Pad :: integer()) -> binary(). pad_to_bin(Num, Pad) when Pad =< 0 andalso is_binary(Num) -> Num; pad_to_bin(Num, Pad) when not is_binary(Num) -> NumBin = to_bin(Num), pad_to_bin(NumBin, Pad - byte_size(NumBin)); pad_to_bin(Num, Pad) -> pad_to_bin(<<"0", Num/binary>>, Pad - 1). -spec flooring(number()) -> integer(). -ifdef(NATIVE_FLOOR). flooring(X) -> floor(X). -else. flooring(X) when X < 0 -> T = trunc(X), case X - T == 0 of true -> T; false -> T - 1 end; flooring(X) -> trunc(X). -endif. -spec ceiling(number()) -> integer(). -ifdef(NATIVE_CEIL). ceiling(X) -> ceil(X). -else. ceiling(X) when X < 0 -> trunc(X); ceiling(X) -> T = trunc(X), case X - T == 0 of true -> T; false -> T + 1 end. -endif. -spec urand() -> float(). -ifndef(TEST). %% @doc Show random decimal number. This function makes possible to overload the %% normal Erlang behaviour to make tests reliable. %% @end urand() -> rand:uniform(). -else. %% @private urand() -> 0.72643441. -endif. -spec pow(number(), number()) -> number(). %% @doc calculates the power of a number letting to be that number in integer format %% if that's a positive number or it's powered by 0. pow(_, 0) -> 1; pow(N, M) when M > 0 -> lists:foldl(fun(_, Acc) -> N * Acc end, 1, lists:seq(1, M)); pow(N, M) when M < 0 -> 1 / pow(N, -M). -spec instance_of(ephp:context_id(), mixed() | class(), DataType :: binary()) -> boolean(). instance_of(_Context, Array, <<"array">>) when ?IS_ARRAY(Array) -> true; instance_of(_Context, Boolean, <<"bool">>) when is_boolean(Boolean) -> true; instance_of(_Context, String, <<"string">>) when is_binary(String) -> true; instance_of(_Context, Float, <<"float">>) when is_float(Float) -> true; instance_of(_Context, Int, <<"int">>) when is_integer(Int) -> true; % TODO: % instance_of(Callable, <<"callable">>) -> % false; instance_of(Context, Self, <<"self">>) -> %% TODO scope error if active_class is not defined SelfClass = ephp_context:get_active_class(Context), instance_of(Context, Self, SelfClass); instance_of(Context, ObjRef, Name) when ?IS_OBJECT(ObjRef) -> #ephp_object{class = Class} = ephp_object:get(ObjRef), #class{parents = Parents, name = InstanceName} = Class, Classes = ephp_context:get_classes(Context), case ephp_class:get(Classes, Class#class.namespace, Name) of {ok, #class{name = ClassName}} -> lists:any(fun(F) -> F() end, [fun() -> ClassName =:= InstanceName end, fun() -> lists:member(ClassName, Parents) end]); {error, _} -> false end; instance_of(_Context, #class{name = CName}, CName) -> true; instance_of(_Context, #class{parents = Parents}, CName) -> lists:member(CName, Parents); instance_of(_Context, _Data, _Type) -> false.