%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1999-2018. All Rights Reserved.
%% 
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%% 
%% %CopyrightEnd%
%%

-module(beam_literals_SUITE).
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 
	 init_per_group/2,end_per_group/2]).
-export([putting/1, matching_smalls/1, matching_smalls_jt/1,
	 matching_bigs/1, matching_more_bigs/1,
	 matching_bigs_and_smalls/1, badmatch/1, case_clause/1,
	 receiving/1, literal_type_tests/1,
	 put_list/1, fconv/1, literal_case_expression/1,
	 increment/1]).

-include_lib("common_test/include/ct.hrl").

suite() -> [{ct_hooks,[ts_install_cth]}].

all() -> 
    [putting, matching_smalls, matching_smalls_jt,
     matching_bigs, matching_more_bigs,
     matching_bigs_and_smalls, badmatch, case_clause,
     receiving, literal_type_tests, put_list, fconv,
     literal_case_expression, increment].

groups() -> 
    [].

init_per_suite(Config) ->
    Config.

end_per_suite(_Config) ->
    ok.

init_per_group(_GroupName, Config) ->
    Config.

end_per_group(_GroupName, Config) ->
    Config.


%% Test creating lists and tuples containing big number literals.
putting(Config) when is_list(Config) ->
    -773973888575883407313908 = chksum(putting1(8987697898797)).

putting1(X) ->
    {8797987987987987872256443, [1324483773773], {3.1415, 2.71, [2.5, 35.125|9.31]},
     [X|349873987387373],
     [329878349873|-387394729872], -773973937933873929749873}.

%% Test matching of a few big number literals (in Beam select_val/3 will NOT be used).
matching_bigs(Config) when is_list(Config) ->
    a = matching1(3972907842873739),
    b = matching1(-389789298378939783333333333333333333784),
    other = matching1(3141699999999999999999999999999999999),
    other = matching1(42).

%% Test matching small numbers (both positive and negative).
matching_smalls(Config) when is_list(Config) ->
    a = m_small(-42),
    b = m_small(0),
    c = m_small(105),
    d = m_small(-13),
    e = m_small(337848),
    other = m_small(324),
    other = m_small(-7),
    ok.

m_small(-42) -> a;
m_small(0) -> b;
m_small(105) -> c;
m_small(-13) -> d;
m_small(337848) -> e;
m_small(_) -> other.

%% Test matching small numbers (both positive and negative).
%% Make sure that a jump table is used.
matching_smalls_jt(Config) when is_list(Config) ->
    a = m_small_jt(-2),
    b = m_small_jt(-1),
    c = m_small_jt(0),
    d = m_small_jt(2),
    e = m_small_jt(3),
    other = m_small(324),
    other = m_small(-7),
    ok.

m_small_jt(-2) -> a;
m_small_jt(-1) -> b;
m_small_jt(0) -> c;
m_small_jt(2) -> d;
m_small_jt(3) -> e;
m_small_jt(_) -> other.

%% Big numbers, no select_val.

matching1(3972907842873739) -> a;
matching1(-389789298378939783333333333333333333784) -> b;
matching1(_) -> other.


%% Test matching of a big number literals (in Beam, a select_val/3 instruction will be used)
matching_more_bigs(Config) when is_list(Config) ->
    a = matching2(-999766349740978337),
    b = matching2(9734097866575478),
    c = matching2(-966394677364879734),
    d = matching2(13987294872948990),
    e = matching2(777723896192459245),
    other = matching2(7),
    other = matching2(39789827988888888888888888888347474444444444444444444).

%% Big numbers with select_val.

matching2(-999766349740978337) -> a;
matching2(9734097866575478) -> b;
matching2(-966394677364879734) -> c;
matching2(13987294872948990) -> d;
matching2(777723896192459245) -> e;
matching2(_) -> other.

%% Test matching of a mix of big numbers and literals.
matching_bigs_and_smalls(Config) when is_list(Config) ->
    a = matching3(38472928723987239873873),
    b = matching3(0),
    c = matching3(-3873973932710954671207461057614287561348756348743634876436784367873),
    d = matching3(3978429867297393873),
    e = matching3(42),
    f = matching3(-4533),
    other = matching3(77),
    other = matching3(39274120984379249874219748).

%% Mixed small and big.

matching3(38472928723987239873873) -> a;
matching3(0) -> b;
matching3(-3873973932710954671207461057614287561348756348743634876436784367873) -> c;
matching3(3978429867297393873) -> d;
matching3(42) -> e;
matching3(-4533) -> f;
matching3(_) -> other.

%% Test literal badmatches with big number and floats.
badmatch(Config) when is_list(Config) ->
    %% We are satisfied if we can load this module and run it.
    Big = id(32984798729847892498297824872982972978239874),
    Float = id(3.1415927),
    catch a = Big,
    catch b = Float,
    {'EXIT',{{badmatch,3879373498378993387},_}} =
	   (catch c = 3879373498378993387),
    {'EXIT',{{badmatch,7.0},_}} = (catch d = 7.0),
    case Big of
        Big -> ok
    end,
    case Float of
        Float -> ok
    end,
    ok.

case_clause(Config) when is_list(Config) ->
    {'EXIT',{{case_clause,337.0},_}} = (catch case_clause_float()),
    {'EXIT',{{try_clause,42.0},_}} = (catch try_case_clause_float()),
    {'EXIT',{{case_clause,37932749837839747383847398743789348734987},_}} =
	(catch case_clause_big()),
    {'EXIT',{{try_clause,977387349872349870423364354398566348},_}} =
	(catch try_case_clause_big()),
    ok.

case_clause_float() ->
    case 337.0 of
	blurf -> ok
    end.

try_case_clause_float() ->
    try 42.0 of
	blurf -> ok
    catch _:_ ->
	    error
    end.

case_clause_big() ->
    case 37932749837839747383847398743789348734987 of
	blurf -> ok
    end.

try_case_clause_big() ->
    try 977387349872349870423364354398566348 of
	blurf -> ok
    catch _:_ ->
	    error
    end.

%% Test receive with a big number literal (more than 27 bits, less than 32 bits).
receiving(Config) when is_list(Config) ->
    Self = self(),
    spawn(fun() -> Self ! here_is_a_message end),
    ok = receive
	     here_is_a_message ->
		 ok
	 after 16#f1234567 ->
		 timeout
	 end.

%% Test type tests on literal values.
literal_type_tests(Config) when is_list(Config) ->
    %% Generate an Erlang module with all different type of type tests.
    Tests = make_test([{T, L} || T <- type_tests(), L <- literals()]),
    Mod = literal_test,
    Anno = erl_anno:new(0),
    Func = {function, Anno, test, 0, [{clause,Anno,[],[],Tests}]},
    Form = [{attribute,Anno,module,Mod},
            {attribute,Anno,compile,export_all},
            Func, {eof,Anno}],

    %% Print generated code for inspection.
    lists:foreach(fun (F) -> io:put_chars([erl_pp:form(F),"\n"]) end, Form),

    %% Test compile:form/1.  This implies full optimization (default).
    {ok,Mod,Code1} = compile:forms(Form),
    {module,Mod} = code:load_binary(Mod, Mod, Code1),
    Mod:test(),
    true = code:delete(Mod),
    code:purge(Mod),
			       
    %% Test compile:form/2.  Turn off all optimizations.
    {ok,Mod,Code2} = compile:forms(Form, [binary,report,time,
						no_copt,no_postopt]),
    {module,Mod} = code:load_binary(Mod, Mod, Code2),
    Mod:test(),
    true = code:delete(Mod),
    code:purge(Mod),
    ok.

make_test([{is_function=T,L}|Ts]) ->
    [guard_test(T, L),guard_test(T, 0, L),body_test(T, L),body_test(T, 0, L)|make_test(Ts)];
make_test([{T,L}|Ts]) ->
    [guard_test(T, L),body_test(T, L)|make_test(Ts)];
make_test([]) -> [].

guard_test(_, L) when is_function(L) ->
    %% Skip guard tests with exports - they are not literals
    {atom,erl_anno:new(0),true};
guard_test(T, L) ->
    S = io_lib:format("begin io:format(\"~~p~n\", [{~p,~p}]), if ~w(~w) -> true; true -> false end end. ", [T, L, T, L]),
    {Val,Expr} = eval_string(S),
    Anno = erl_anno:new(0),
    {match,Anno,{atom,Anno,Val},Expr}.

guard_test(_, _, L) when is_function(L) ->
    %% Skip guard tests with exports - they are not literals
    {atom,erl_anno:new(0),true};
guard_test(T, A, L) ->
    S = io_lib:format("begin io:format(\"~~p~n\", [{~p,~p,~p}]), if ~w(~w, ~w) -> true; true -> false end end. ", [T,L,A,T,L,A]),
    {Val,Expr} = eval_string(S),
    Anno = erl_anno:new(0),
    {match,Anno,{atom,Anno,Val},Expr}.

body_test(T, L) ->
    S = io_lib:format("begin io:format(\"~~p~n\", [{~p,~p}]), ~w(~w) end. ", [T,L,T,L]),
    {Val,Expr} = eval_string(S),
    Anno = erl_anno:new(0),
    {match,Anno,{atom,Anno,Val},Expr}.

body_test(T, A, L) ->
    S = io_lib:format("begin io:format(\"~~p~n\", [{~p,~p,~p}]), ~w(~w,~w) end. ", [T,L,A,T,L,A]),
    {Val,Expr} = eval_string(S),
    Anno = erl_anno:new(0),
    {match,Anno,{atom,Anno,Val},Expr}.

eval_string(S) ->
    {ok,Toks,_Line} = erl_scan:string(lists:flatten(S)),
    {ok,E} = erl_parse:parse_exprs(Toks),
    {value,Val,_Bs} = erl_eval:exprs(E, []),
    {Val,hd(E)}.

literals() ->
    [42,
     3.14,
     -3,
     32982724987789283473473838474,
     [],
     "abc",
     <<"abc">>,
     {},
     xxxx,
     fun erlang:erase/0].

type_tests() ->
    [is_boolean,
     is_integer,
     is_float,
     is_number,
     is_atom,
     is_list,
     is_tuple,
     is_pid,
     is_reference,
     is_port,
     is_binary,
     is_function].

put_list(Config) when is_list(Config) ->
    %% put_list x0 Literal Reg
    [Config|8739757395764] = put_list_rqr(Config),
    {[Config|7779757395764],Config} = put_list_rqx(Config),
    [Config|98765432100000] = put_list_rqy(Config),

    %% put_list x Literal Reg
    [Config|16#FFFFF77777137483769] = put_list_xqr(ignore, Config),
    {[Config|16#AAAAAFFFFF77777],{a,b},Config} =  put_list_xqx({a,b}, Config),
    [Config|12777765432979879] = put_list_xqy(ignore, Config),

    %% put_list y Literal Reg
    [Config|17424134793676869867] = put_list_yqr(Config),
    {[Config|77424134793676869867],Config} = put_list_yqx(Config),
    {Config,[Config|16#BCDEFF4241676869867]} = put_list_yqy(Config),

    %% put_list Literal x0 Reg
    [42.0|Config] = put_list_qrr(Config),
    [Config,42.0|Config] = put_list_qrx(Config),
    [100.0|Config] = put_list_qry(Config),

    %% put_list Literal x1 Reg
    [127.0|Config] = put_list_qxr({ignore,me}, Config),
    [Config,130.0|Config] = put_list_qxx(ignore, Config),
    [99.0|Config] = put_list_qxy(Config),

    %% put_list Literal y0 Reg
    [200.0|Config] = put_list_qyr(Config),
    [Config,210.0|Config] = put_list_qyx(Config),
    [[300.0|Config]|Config] = put_list_qyy(Config),

    ok.
    
%% put_list x0 Literal x0
put_list_rqr(Config) -> [Config|8739757395764].

%% put_list x0 Literal x1
put_list_rqx(Config) -> {[Config|7779757395764],Config}.

%% put_list x0 Literal y0
put_list_rqy(Config) ->
    Res = [Config|98765432100000],
    id(42),
    Res.

%% put_list x1 Literal x0
put_list_xqr(_, Config) -> [Config|16#FFFFF77777137483769].

%% put_list x1 Literal x2
put_list_xqx(A, Config) -> {[Config|16#AAAAAFFFFF77777],A,Config}.

%% put_list x1 Literal y0
put_list_xqy(_, Config) ->
    Res = [Config|12777765432979879],
    id(42),
    Res.

%% put_list y0 Literal x0
put_list_yqr(Config) ->    
    id(Config),
    [Config|17424134793676869867].

%% put_list y0 Literal x1
put_list_yqx(Config) ->    
    id(Config),
    {[Config|77424134793676869867],Config}.

%% put_list y1 Literal y0
put_list_yqy(Config) ->
    id(Config),
    Res = [Config|16#BCDEFF4241676869867],
    id(Config),
    {Config,Res}.

%% put_list Literal x0 x0
put_list_qrr(Config) ->
    [42.0|Config].

%% put_list Literal x0 x1
put_list_qrx(Config) ->
    [Config,42.0|Config].

%% put_list Literal x0 y0
put_list_qry(Config) ->
    Res = [100.0|Config],
    id(0),
    Res.

%% put_list Literal x1 x0
put_list_qxr(_, Config) ->
    [127.0|Config].

%% put_list Literal x1 x2
put_list_qxx(_, Config) ->
    [Config,130.0|Config].

%% put_list Literal x1 y0
put_list_qxy(Config) ->
    Res = [99.0|Config],
    id(0),
    Res.

%% put_list Literal y0 x0
put_list_qyr(Config) ->    
    id(Config),
    [200.0|Config].

%% put_list Literal y0 x1
put_list_qyx(Config) ->    
    id(Config),
    [Config,210.0|Config].

%% put_list Literal y1 y0
put_list_qyy(Config) ->
    id(Config),
    Res = [300.0|Config],
    id(Config),
    [Res|Config].

fconv(Config) when is_list(Config) ->
    5.0 = fconv_1(-34444444450.0),
    13.0 = fconv_2(7.0),
    ok.

fconv_1(F) when is_float(F) ->
    34444444455 + F.

fconv_2(F) when is_float(F) ->
    6.0 + F.

literal_case_expression(Config) when is_list(Config) ->
    DataDir = proplists:get_value(data_dir, Config),
    Src = filename:join(DataDir, "literal_case_expression"),
    {ok,literal_case_expression=Mod,Code} = compile:file(Src, [from_asm,binary]),
    {module,Mod} = code:load_binary(Mod, Src, Code),
    ok = Mod:x(),
    ok = Mod:y(),
    ok = Mod:zi1(),
    ok = Mod:zi2(),
    ok = Mod:za1(),
    ok = Mod:za2(),
    true = code:delete(Mod),
    code:purge(Mod),
    ok.

%% Test the i_increment instruction.
increment(Config) when is_list(Config) ->
    %% In the 32-bit emulator, Neg32 can be represented as a small,
    %% but -Neg32 cannot. Therefore the i_increment instruction must
    %% not be used in the subtraction that follows (since i_increment
    %% cannot handle a bignum literal).
    Neg32 = -(1 bsl 27),
    Big32 = id(1 bsl 32),
    Result32 = (1 bsl 32) + (1 bsl 27),
    Result32 = Big32 + (1 bsl 27),
    Result32 = Big32 - Neg32,

    %% Same thing, but for the 64-bit emulator.
    Neg64 = -(1 bsl 59),
    Big64 = id(1 bsl 64),
    Result64 = (1 bsl 64) + (1 bsl 59),
    Result64 = Big64 + (1 bsl 59),
    Result64 = Big64 - Neg64,

    %% Test error handling for the i_increment instruction.
    Bad = id(bad),
    {'EXIT',{badarith,_}} = (catch Bad + 42),

    %% Small operands, but a big result.
    Res32 = 1 bsl 27,
    Small32 = id(Res32-1),
    Res32 = Small32 + 1,
    Res64 = 1 bsl 59,
    Small64 = id(Res64-1),
    Res64 = Small64 + 1,
    ok.

%% Help functions.

chksum(Term) ->
    chksum(Term, 0).

chksum([List|T], Sum) when is_list(List) ->
    chksum(T, chksum(List, Sum));
chksum([H|T], Sum) ->
    chksum(T, chksum(H, Sum));
chksum([], Sum) -> Sum;
chksum(Tuple, Sum) when is_tuple(Tuple) ->
    chksum(tuple_to_list(Tuple), Sum);
chksum(Int, Sum) when is_integer(Int) ->
    Sum * 5 + Int;
chksum(Other, Sum) ->
    erlang:phash2([Other|Sum], 39729747).

id(I) -> I.
