diff --git a/src/comMisc/utList.erl b/src/comMisc/utList.erl new file mode 100644 index 0000000..edbf262 --- /dev/null +++ b/src/comMisc/utList.erl @@ -0,0 +1,238 @@ +-module(utList). + +-define(TRUE, true). +-define(FALSE, false). +-define(BREAK, break). +-define(BREAK(Value), {?BREAK, Value}). +-define(CONTINUE, continue). +-define(CONTINUE(Value), {?CONTINUE, Value}). +-define(UNDEFINED, undefined). + +-compile([export_all, nowarn_export_all]). + +while(Fun, CurData) -> + case Fun(CurData) of + ?BREAK -> CurData; + ?BREAK(BreakData) -> BreakData; + ?CONTINUE -> + while(Fun, CurData); + ?CONTINUE(Continue) -> + while(Fun, Continue) + end. + +%% For loop. +for(N, Fun) -> + for(1, N, 1, Fun). + +for(Start, End, Fun) -> + for(Start, End, 1, Fun). + +for(End, End, _Step, Fun) -> + Fun(End); +for(Start, End, Step, Fun) -> + Fun(Start), + for(Start + Step, End, Step, Fun). + + +%% 带返回状态的for循环 @return {ok, State} +forWithState(Index, Max, _Fun, State) when Index > Max -> + {ok, State}; +forWithState(Max, Max, Fun, State) -> + Fun(Max, State); +forWithState(Index, Max, Fun, State) -> + {ok, NewState} = Fun(Index, State), + forWithState(Index + 1, Max, Fun, NewState). + +forWhile(Fun, State) -> + forWhile(?CONTINUE, Fun, State). +forWhile(?BREAK, _Fun, State) -> + State; +forWhile(?CONTINUE, Fun, State) -> + {Next, NewState} = Fun(State), + forWhile(Next, Fun, NewState). + +%% 可以重复获取 +randFormList([]) -> + false; +randFormList(List) -> + lists:nth(rand:uniform_s(1, length(List)), List). + +%% 不能重复获取 +randFormListOnce([]) -> + {false, []}; +randFormListOnce(List) -> + Nth = rand:uniform_s(1, length(List)), + NthData = lists:nth(Nth, List), + {NthData, lists:delete(NthData, List)}. + +randDelete(RemainList) -> + Pos = rand:uniform_s(1, length(RemainList)), + remove_nth(Pos, RemainList). + +randDelete(DelCount, List) -> + FunDel = + fun(_, RemainList) -> + RemainList(RemainList) + end, + lists:foldl(FunDel, List, lists:seq(1, DelCount)). + +%%随机从集合中选出指定个数的元素length(List) >= Num +%%[1,2,3,4,5,6,7,8,9]中选出三个不同的数字[1,2,4] +getRandFromList(Num, List) -> + ListSize = length(List), + FunDel = + fun(N, List1) -> + Random = rand:uniform_s(1, (ListSize - N + 1)), + Elem = lists:nth(Random, List1), + lists:delete(Elem, List1) + end, + Result = lists:foldl(FunDel, List, lists:seq(1, Num)), + List -- Result. + + +%%打乱列表函数 List =[] 返回 打乱后列表 List2 +confuseList(List) -> + confuseList(List, []). + +confuseList(List, Result) -> + confuseList(List, rand:uniform_s(1, length(List)), Result). + +confuseList(_List, 0, Result) -> + Result; +confuseList(List, Nth, Result) -> + {NthData, Remain} = extract_member(rand:uniform_s(1, Nth), List), + confuseList(Remain, length(Remain), [NthData | Result]). + +%%提取列表中第Nth个元素,{NthData, RemainList} +extract_member(Nth, List) -> + extract_member(Nth, List, []). + +extract_member(1, List1, List2) -> + case List1 of + [R] -> + {R, List2}; + [R | L] -> + {R, List2 ++ L} + end; + +extract_member(Nth, [R | List1], List2) -> + extract_member(Nth - 1, List1, List2 ++ [R]). + +%%根数Tuple元素数量,把List转成Tuple 返回:{TupleList, RemainList} +list_to_tuple(List, TupleCount) -> + list_to_tuple(List, TupleCount, []). +list_to_tuple(List, TupleCount, Result) when length(List) >= TupleCount -> + {List1, List2} = lists:split(TupleCount, List), + list_to_tuple(List2, TupleCount, [list_to_tuple(List1) | Result]); +list_to_tuple(List, _TupleCount, Result) -> + {lists:reverse(Result), List}. + +%%计算元素在列表中出现几次(5, [1,2,3,5,5,5,3]) ----> 3 +repeatCount(Element, List) -> + repeatCount(Element, List, 0). +repeatCount(Element, List, Count) -> + case lists:member(Element, List) of + false -> Count; + true -> + repeatCount(Element, lists:delete(Element, List), Count + 1) + end. + +remove_nth(RemainList, Pos) -> + Head = lists:sublist(RemainList, 1, Pos - 1), + End = lists:sublist(RemainList, Pos + 1, length(RemainList)), + Head ++ End. + +%%删除指定元素返回:(5, [1,2,4,4,5,5,4,5]) -> [1,2,4,4,4] +remove_element(Element, List) -> + case lists:member(Element, List) of + false -> List; + true -> remove_element(Element, lists:delete(Element, List)) + end. + +%%删除重复元素 [1,2,3,2,3,4] ---> [1,2,3,4] +remove_repeat(List) -> + remove_repeat(List, []). + +remove_repeat([], Result) -> + Result; +remove_repeat([L | List], Result) -> + case lists:member(L, Result) of + false -> remove_repeat(List, Result ++ [L]); + true -> remove_repeat(List, Result) + end. + + + +%%根据Key去查List中元素的Nth位相等的匹配到的元素Fun(Element)返回 +find_elements(Key, Nth, List, Fun) -> + InnerFun = fun(Element) -> + case element(Nth, Element) =:= Key of + true -> + {true, Fun(Element)}; + false -> + false + end + end, + lists:filtermap(InnerFun, List). + +%%对list进行去重,排序 +%%Replicat 0不去重,1去重 +%%Sort 0不排序,1排序 +filter_list(List, Replicat, Sort) -> + if Replicat == 0 andalso Sort == 0 -> + List; + true -> + if Replicat == 1 andalso Sort == 1 -> + lists:usort(List); + true -> + if Sort == 1 -> + lists:sort(List); + true -> + lists:reverse(filter_replicat(List, [])) + end + end + end. + +%%list去重 +filter_replicat([], List) -> + List; +filter_replicat([H | Rest], List) -> + Bool = lists:member(H, List), + List1 = + if Bool == true -> + [[] | List]; + true -> + [H | List] + end, + List2 = lists:filter(fun(T) -> T =/= [] end, List1), + filter_replicat(Rest, List2). + +%%交集 +get_intersection_list(A, B) when is_list(A) andalso is_list(B) -> + lists:filter(fun(X) -> lists:member(X, A) end, B). + +%%并集 +get_unite_list(A, B) when is_list(A) andalso is_list(B) -> + C = A ++ B, + lists:usort(C). + +%%差集 +get_subtract_list(A, B) when is_list(A) andalso is_list(B) -> + Insection = get_intersection_list(A, B), + Unite = get_unite_list(A, B), + lists:filter(fun(X) -> lists:member(X, Insection) =:= false end, Unite). + + +merge(L) -> + merge(L, []). +merge([{Key, Value} | T], AccList) -> + NewAccList = + case lists:keyfind(Key, 1, AccList) of + false -> + [{Key, Value} | AccList]; + {Key, OldValue} -> + lists:keyreplace(Key, 1, AccList, {Key, OldValue + Value}) + end, + merge(T, NewAccList); +merge([], AccList) -> + AccList. diff --git a/src/comMisc/utMisc.erl b/src/comMisc/utMisc.erl new file mode 100644 index 0000000..e029a41 --- /dev/null +++ b/src/comMisc/utMisc.erl @@ -0,0 +1,74 @@ +-module(utMisc). + +-compile([export_all, nowarn_export_all]). + +%% 日志记录函数 +fileLog(T, F, A, Mod, Line) -> + {ok, Fl} = file:open("logs/error_log.txt", [write, append]), + Format = list_to_binary("#" ++ T ++ " ~s[~w:~w] " ++ F ++ "\r\n~n"), + {{Y, M, D}, {H, I, S}} = erlang:localtime(), + Date = list_to_binary([integer_to_list(Y), "-", integer_to_list(M), "-", integer_to_list(D), " ", integer_to_list(H), ":", integer_to_list(I), ":", integer_to_list(S)]), + io:format(Fl, unicode:characters_to_list(Format), [Date, Mod, Line] ++ A), + file:close(Fl). + +compile_base_data(Table, ModName, IDPoses) -> + ModNameString = com_util:term_to_string(ModName), + HeadString = + "-module(" ++ ModNameString ++ "). + -compile(export_all). + ", + BaseDataList = db_base:select_all(Table, "*", []), + ContentString = + lists:foldl(fun(BaseData0, PreString) -> + FunChange = + fun(Field) -> + if is_integer(Field) -> Field; + true -> + case com_util:bitstring_to_term(Field) of + undefined -> + Field; + Term -> + Term + end + end + end, + BaseData = [FunChange(Item) || Item <- BaseData0], + Base = list_to_tuple([Table | BaseData]), + BaseString = com_util:term_to_string(Base), + IDs = [element(Pos, Base) || Pos <- IDPoses], + IDList0 = lists:foldl(fun(ID, PreString2) -> + IDList = + if erlang:is_integer(ID) -> + integer_to_list(ID); + true -> + ID + end, + PreString2 ++ "," ++ IDList + end, [], IDs), + [_ | IDList] = IDList0, + PreString ++ + "get(" ++ + IDList ++ + ") ->" ++ + BaseString ++ + "; + " + end + , "", BaseDataList), + + _List0 = [",_" || _Pos <- IDPoses], + [_ | _List] = lists:flatten(_List0), + ErrorString = "get(" ++ _List ++ ") -> undefined. + ", + FinalString = HeadString ++ ContentString ++ ErrorString, + %% ?PRINT("string=~s~n",[FinalString]), + try + {Mod, Code} = dynamic_compile:from_string(FinalString), + code:load_binary(Mod, ModNameString ++ ".erl", Code) + catch + Type:Error -> io:format("Error compiling (~p): ~p~n", [Type, Error]) + end, + ok. + + + diff --git a/src/comMisc/utRate.erl b/src/comMisc/utRate.erl new file mode 100644 index 0000000..9908dea --- /dev/null +++ b/src/comMisc/utRate.erl @@ -0,0 +1,129 @@ +-module(utRate). + +-export([happen/1, + choose/1, + choose_n_uniq/2, + range/1, + collect/2, + select_n/2, %% return N unique items + collect_n/2, %% return N items, same item may appear than once + collect_one/1, + rand_list/1, + rand_list_n/2]). + +-on_load(init/0). + +init() -> + % mtwist:seed(time_utils:now()), + rand:seed(exs1024, erlang:timestamp()), + ok. + +happen(Rate) when Rate == 1.0 -> true; +happen(Rate) when Rate < 1 -> + happen(Rate * 10000); +happen(Rate) -> + % RandomValue = mtwist:uniform(10000), + RandomValue = rand:uniform(10000), + compare(RandomValue, Rate). + +%% Rates = [{RateA, ValueA}, {RateB, ValueB}, {default, DefaultValue}], +choose([{_Rate, Value}]) -> Value; +choose(Rates) -> + {_Rate, Value} = do_choose(Rates), + Value. + +do_choose(Rates) -> + MaxValue = lists:foldl(fun({Rate, _}, Result) -> + Result + Rate + end, 0, Rates), + RandomValue = rand:uniform(MaxValue), + {Rate, Value} = choose(Rates, RandomValue, 0), + {Rate, Value}. + +choose([{Rate, Value}|Rates], RandomValue, Offset) -> + case RandomValue =< Rate + Offset of + true -> {Rate, Value}; + false -> choose(Rates, RandomValue, Offset + Rate) + end. + +choose_n_uniq(Rates, N) -> + choose_n_uniq(Rates, N, []). + +choose_n_uniq(_Rates, 0, Result) -> Result; +choose_n_uniq(Rates, N, Result) -> + {Rate, Value} = do_choose(Rates), + NRates = lists:delete({Rate, Value}, Rates), + choose_n_uniq(NRates, N - 1, [Value|Result]). + +%% Range = [{RateA, ValueA}, {RateB, ValueB}, {default, DefaultValue}], +%% select the happened Rate +range(Range) -> + % select(mtwist:uniform(10000), Range). + select(rand:uniform(10000), Range). + +select(RandomValue, [{Rate, Value}|_Range]) when RandomValue =< Rate -> Value; +select(_RandomValue, [{default, Value}]) -> Value; +select(RandomValue, [{_Rate, _Value}|Range]) -> + select(RandomValue, Range). + +compare(A, B) when A < B -> true; +compare(_A, _B) -> false. + + +%% Rand return N elements from Rates +%% Rates = [{RateA, ValueA}, {RateB, ValueB}, {RateC, ValueC}], +%% N =< length(Rates) +collect(Rates, N) -> + MaxValue = lists:foldl(fun({Rate, _}, Result) -> + Result + Rate + end, 0, Rates), + collect(N, Rates, MaxValue, []). + +collect(0, _Rates, _MaxValue, Result) -> Result; +collect(N, Rates, MaxValue, Result) -> + % RandValue = mtwist:uniform(MaxValue), + RandValue = rand:uniform(MaxValue), + {Rate, Value} = choose(Rates, RandValue, 0), + NewRates = lists:delete({Rate, Value}, Rates), + collect(N - 1, NewRates, MaxValue - Rate, [Value|Result]). + +%% Select N elements from List +select_n(List, N) -> select_n(List, N, []). + +select_n([], _N, Result) -> Result; +select_n(_List, 0, Result) -> Result; +select_n(List, N, Result) -> + Len = length(List), + % Index = mtwist:uniform(Len) + 1, + Index = rand:uniform(Len), + Elem = lists:nth(Index, List), + select_n(lists:delete(Elem, List), N - 1, [Elem|Result]). + +collect_n(List, N) -> collect_n(List, N, []). + +collect_n(_List, 0, Result) -> Result; +collect_n(List, N, Result) -> + Len = length(List), + % Index = trunc(mtwist:uniform(Len)) + 1, + Index = rand:uniform(Len), + Elem = lists:nth(Index, List), + collect_n(List, N - 1, [Elem|Result]). + +collect_one(List) -> + [Elem] = collect_n(List, 1), + Elem. + +rand_list(List) -> + [X || {_, X} <- lists:sort([{rand:uniform(), N} || N <- List])]. + +rand_list_n(List, Limit) -> + SortedList = lists:sort([{rand:uniform(), N} || N <- List]), + {Result, _} = misc_utils:each(fun({_Fator, Elem}, {Acc, Idx}) -> + if + Idx =:= Limit -> + {break, {Acc, Idx}}; + Idx < Limit -> + {[Elem|Acc], Idx + 1} + end + end, {[], 0}, SortedList), + Result. diff --git a/src/dataType/utTypeCast.erl b/src/dataType/utTypeCast.erl index c4eadd2..4551591 100644 --- a/src/dataType/utTypeCast.erl +++ b/src/dataType/utTypeCast.erl @@ -109,4 +109,37 @@ string_to_term(String) -> end; _Error -> undefined - end. \ No newline at end of file + end. + +%% 列表转utf8编码的列表 +listToUtfString(List) -> + unicode:characters_to_list(erlang:list_to_binary(List), utf8). + +%%汉字unicode编码范围 0x4e00 - 0x9fa5 +% (4 * 16 * 16 * 16 + 14 * 16 * 16) +-define(UNICODE_CHINESE_BEGIN, 16#4e00). +% (9 * 16 * 16 * 16 + 15 * 16 * 16 + 10 * 16 + 5) +-define(UNICODE_CHINESE_END, 16#9fa5). + +%% desc 获取字符串汉字和非汉字的个数 +%% parm UTF8String UTF8编码的字符串 +%% return {汉字个数,非汉字个数} +getChineseNum(UTF8String) -> + UnicodeList = unicode:characters_to_list(list_to_binary(UTF8String)), + Fun = fun(Num, {Sum}) -> + case Num >= ?UNICODE_CHINESE_BEGIN andalso Num =< ?UNICODE_CHINESE_END of + true -> + {Sum + 1}; + false -> + {Sum} + end + end, + {ChineseCount} = lists:foldl(Fun, {0}, UnicodeList), + OtherCount = length(UnicodeList) - ChineseCount, + {ChineseCount, OtherCount}. + +termToBase64(Term) -> + base64:encode(term_to_binary(Term)). + +base64ToTerm(Base64String) -> + binary_to_term(base64:decode(Base64String)). diff --git a/src/dynamicCompile/dynamic_compile.erl b/src/dynamicCompile/dynamic_compile.erl new file mode 100644 index 0000000..be24da2 --- /dev/null +++ b/src/dynamicCompile/dynamic_compile.erl @@ -0,0 +1,349 @@ +%%%------------------------------------------------------------------- +%%% @author root +%%% @copyright (C) 2015, +%%% @doc +%%% +%%% @end +%%% Created : 30. 五月 2015 下午8:32 +%%%------------------------------------------------------------------- +%% Copyright (c) 2007 +%% Mats Cronqvist +%% Chris Newcombe +%% Jacob Vorreuter +%% +%% Permission is hereby granted, free of charge, to any person +%% obtaining a copy of this software and associated documentation +%% files (the "Software"), to deal in the Software without +%% restriction, including without limitation the rights to use, +%% copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the +%% Software is furnished to do so, subject to the following +%% conditions: +%% +%% The above copyright notice and this permission notice shall be +%% included in all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +%% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +%% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +%% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +%% OTHER DEALINGS IN THE SOFTWARE. + +%%%------------------------------------------------------------------- +%%% File : dynamic_compile.erl +%%% Description : +%%% Authors : Mats Cronqvist +%%% Chris Newcombe +%%% Jacob Vorreuter +%%% TODO : +%%% - add support for limit include-file depth (and prevent circular references) +%%% prevent circular macro expansion set FILE correctly when -module() is found +%%% -include_lib support $ENVVAR in include filenames +%%% substitute-stringize (??MACRO) +%%% -undef/-ifdef/-ifndef/-else/-endif +%%% -file(File, Line) +%%%------------------------------------------------------------------- +-module(dynamic_compile). + +%% API +-export([init/0, load_from_string/1, load_from_string/2]). +-export([from_string/1, from_string/2, get_forms_from_src/1]). + +-import(lists, [reverse/1, keyreplace/4]). + +-export([module_src/1, module_lt/1, modules/0]). + +-record(module, {name = 0, loadtime = 0, src}). + + +init() -> + ets:new(?MODULE, [public, set, named_table, {keypos, #module.name}]), + ok. + +module_src(Module) -> + init_if_notinit(), + case ets:lookup(?MODULE, Module) of + [] -> false; + [#module{src = Src}] -> Src + end. + +module_lt(Module) -> + init_if_notinit(), + case ets:lookup(?MODULE, Module) of + [] -> false; + [#module{loadtime = Lt}] -> Lt + end. + +modules() -> + init_if_notinit(), + ets:tab2list(?MODULE). + +module_new(Module, Src) -> + ets:insert(?MODULE, #module{name = Module, src = Src}). + +init_if_notinit() -> + case ets:info(?MODULE) of + undefined -> + init(); + _ -> + ignore + end. + +%%==================================================================== +%% API +%%==================================================================== +%%-------------------------------------------------------------------- +%% Function: +%% Description: +%% Compile module from string and load into VM +%%-------------------------------------------------------------------- +load_from_string(CodeStr) -> + load_from_string(CodeStr, []). + +load_from_string(CodeStr, CompileFormsOptions) -> + {Mod, Bin} = from_string(CodeStr, CompileFormsOptions), + code:load_binary(Mod, [], Bin), + ok. +%% module_new(Mod, CodeStr). + +%%-------------------------------------------------------------------- +%% Function: +%% Description: +%% Returns a binary that can be used with +%% code:load_binary(Module, ModuleFilenameForInternalRecords, Binary). +%%-------------------------------------------------------------------- +from_string(CodeStr) -> + from_string(CodeStr, []). + +% takes Options as for compile:forms/2 +from_string(CodeStr, CompileFormsOptions) -> + %% Initialise the macro dictionary with the default predefined macros, + %% (adapted from epp.erl:predef_macros/1 + Filename = "compiled_from_string", + %%Machine = list_to_atom(erlang:system_info(machine)), + Ms0 = dict:new(), + % Ms1 = dict:store('FILE', {[], "compiled_from_string"}, Ms0), + % Ms2 = dict:store('LINE', {[], 1}, Ms1), % actually we might add special code for this + % Ms3 = dict:store('MODULE', {[], undefined}, Ms2), + % Ms4 = dict:store('MODULE_STRING', {[], undefined}, Ms3), + % Ms5 = dict:store('MACHINE', {[], Machine}, Ms4), + % InitMD = dict:store(Machine, {[], true}, Ms5), + InitMD = Ms0, + + %% From the docs for compile:forms: + %% When encountering an -include or -include_dir directive, the compiler searches for header files in the following directories: + %% 1. ".", the current working directory of the file server; + %% 2. the base name of the compiled file; + %% 3. the directories specified using the i option. The directory specified last is searched first. + %% In this case, #2 is meaningless. + IncludeSearchPath = ["." | reverse([Dir || {i, Dir} <- CompileFormsOptions])], + {RevForms, _OutMacroDict} = scan_and_parse(CodeStr, Filename, 1, [], InitMD, IncludeSearchPath), + Forms = [{attribute, 0, file, {"compiled_from_string", 0}} | reverse([{eof, 0} | RevForms])], + + %% note: 'binary' is forced as an implicit option, whether it is provided or not. + case compile:forms(Forms, CompileFormsOptions) of + {ok, ModuleName, CompiledCodeBinary} when is_binary(CompiledCodeBinary) -> + {ModuleName, CompiledCodeBinary}; + {ok, ModuleName, CompiledCodeBinary, []} when is_binary(CompiledCodeBinary) -> % empty warnings list + {ModuleName, CompiledCodeBinary}; + {ok, _ModuleName, _CompiledCodeBinary, Warnings} -> + throw({?MODULE, warnings, Warnings}); + Other -> + throw({?MODULE, compile_forms, Other}) + end. + +get_forms_from_src(Src) -> + case scan_and_parse(Src, none, 1, [], dict:new(), []) of + {RevForms, _} -> [{attribute, 0, file, {"compiled_from_string", 0}} | reverse([{eof, 0} | RevForms])] + end. +%%==================================================================== +%% Internal functions +%%==================================================================== +%%% Code from Mats Cronqvist +%%% See http://www.erlang.org/pipermail/erlang-questions/2007-March/025507.html +%%%## 'scan_and_parse' +%%% +%%% basically we call the OTP scanner and parser (erl_scan and +%%% erl_parse) line-by-line, but check each scanned line for (or +%%% definitions of) macros before parsing. +%% returns {ReverseForms, FinalMacroDict} +scan_and_parse([], _CurrFilename, _CurrLine, RevForms, MacroDict, _IncludeSearchPath) -> + {RevForms, MacroDict}; + +scan_and_parse(RemainingText, CurrFilename, CurrLine, RevForms, MacroDict, IncludeSearchPath) -> + case scanner(RemainingText, CurrLine, MacroDict) of + {tokens, NLine, NRemainingText, Toks} -> + {ok, Form0} = erl_parse:parse_form(Toks), + {Form, Forms} = normalize_record(Form0), + scan_and_parse(NRemainingText, CurrFilename, NLine, [Form | Forms ++ RevForms], MacroDict, IncludeSearchPath); + {macro, NLine, NRemainingText, NMacroDict} -> + scan_and_parse(NRemainingText, CurrFilename, NLine, RevForms, NMacroDict, IncludeSearchPath); + {include, NLine, NRemainingText, IncludeFilename} -> + IncludeFileRemainingTextents = read_include_file(IncludeFilename, IncludeSearchPath), + %%io:format("include file ~p contents: ~n~p~nRemainingText = ~p~n", [IncludeFilename,IncludeFileRemainingTextents, RemainingText]), + %% Modify the FILE macro to reflect the filename + %%IncludeMacroDict = dict:store('FILE', {[],IncludeFilename}, MacroDict), + IncludeMacroDict = MacroDict, + + %% Process the header file (inc. any nested header files) + {RevIncludeForms, IncludedMacroDict} = scan_and_parse(IncludeFileRemainingTextents, IncludeFilename, 1, [], IncludeMacroDict, IncludeSearchPath), + %io:format("include file results = ~p~n", [R]), + %% Restore the FILE macro in the NEW MacroDict (so we keep any macros defined in the header file) + %%NMacroDict = dict:store('FILE', {[],CurrFilename}, IncludedMacroDict), + NMacroDict = IncludedMacroDict, + + %% Continue with the original file + scan_and_parse(NRemainingText, CurrFilename, NLine, RevIncludeForms ++ RevForms, NMacroDict, IncludeSearchPath); + done -> + scan_and_parse([], CurrFilename, CurrLine, RevForms, MacroDict, IncludeSearchPath) + end. + +scanner(Text, Line, MacroDict) -> + case erl_scan:tokens([], Text, Line) of + {done, {ok, Toks, NLine}, LeftOverChars} -> + case pre_proc(Toks, MacroDict) of + {tokens, NToks} -> {tokens, NLine, LeftOverChars, NToks}; + {macro, NMacroDict} -> {macro, NLine, LeftOverChars, NMacroDict}; + {include, Filename} -> {include, NLine, LeftOverChars, Filename} + end; + {more, _Continuation} -> + %% This is supposed to mean "term is not yet complete" (i.e. a '.' has + %% not been reached yet). + %% However, for some bizarre reason we also get this if there is a comment after the final '.' in a file. + %% So we check to see if Text only consists of comments. + case is_only_comments(Text) of + true -> + done; + false -> + throw({incomplete_term, _Continuation, Text, Line}) + end + end. + +is_only_comments(Text) -> is_only_comments(Text, not_in_comment). + +is_only_comments([], _) -> true; +is_only_comments([$ | T], not_in_comment) -> + is_only_comments(T, not_in_comment); % skipping whitspace outside of comment +is_only_comments([$\t | T], not_in_comment) -> + is_only_comments(T, not_in_comment); % skipping whitspace outside of comment +is_only_comments([$\n | T], not_in_comment) -> + is_only_comments(T, not_in_comment); % skipping whitspace outside of comment +is_only_comments([$% | T], not_in_comment) -> is_only_comments(T, in_comment); % found start of a comment +is_only_comments(_, not_in_comment) -> false; +% found any significant char NOT in a comment +is_only_comments([$\n | T], in_comment) -> is_only_comments(T, not_in_comment); % found end of a comment +is_only_comments([_ | T], in_comment) -> is_only_comments(T, in_comment). % skipping over in-comment chars + +%%%## 'pre-proc' +%%% +%%% have to implement a subset of the pre-processor, since epp insists +%%% on running on a file. +%%% only handles 2 cases; +%% -define(MACRO, something). +%% -define(MACRO(VAR1,VARN),{stuff,VAR1,more,stuff,VARN,extra,stuff}). +pre_proc([{'-', _}, {atom, _, define}, {'(', _}, {_, _, Name} | DefToks], MacroDict) -> + false = dict:is_key(Name, MacroDict), + case DefToks of + [{',', _} | Macro] -> + {macro, dict:store(Name, {[], macro_body_def(Macro, [])}, MacroDict)}; + [{'(', _} | Macro] -> + {macro, dict:store(Name, macro_params_body_def(Macro, []), MacroDict)} + end; + +pre_proc([{'-', _}, {atom, _, include}, {'(', _}, {string, _, Filename}, {')', _}, {dot, _}], _MacroDict) -> + {include, Filename}; + +pre_proc(Toks, MacroDict) -> + {tokens, subst_macros(Toks, MacroDict)}. + +macro_params_body_def([{')', _}, {',', _} | Toks], RevParams) -> + {reverse(RevParams), macro_body_def(Toks, [])}; +macro_params_body_def([{var, _, Param} | Toks], RevParams) -> + macro_params_body_def(Toks, [Param | RevParams]); +macro_params_body_def([{',', _}, {var, _, Param} | Toks], RevParams) -> + macro_params_body_def(Toks, [Param | RevParams]). + +macro_body_def([{')', _}, {dot, _}], RevMacroBodyToks) -> + reverse(RevMacroBodyToks); +macro_body_def([Tok | Toks], RevMacroBodyToks) -> + macro_body_def(Toks, [Tok | RevMacroBodyToks]). + +subst_macros(Toks, MacroDict) -> + reverse(subst_macros_rev(Toks, MacroDict, [])). + +%% returns a reversed list of tokes +subst_macros_rev([{'?', _}, {_, LineNum, 'LINE'} | Toks], MacroDict, RevOutToks) -> + %% special-case for ?LINE, to avoid creating a new MacroDict for every line in the source file + subst_macros_rev(Toks, MacroDict, [{integer, LineNum, LineNum}] ++ RevOutToks); + +subst_macros_rev([{'?', _}, {_, _, Name}, {'(', _} = Paren | Toks], MacroDict, RevOutToks) -> + case dict:fetch(Name, MacroDict) of + {[], MacroValue} -> + %% This macro does not have any vars, so ignore the fact that the invocation is followed by "(...stuff" + %% Recursively expand any macro calls inside this macro's value + %% TODO: avoid infinite expansion due to circular references (even indirect ones) + RevExpandedOtherMacrosToks = subst_macros_rev(MacroValue, MacroDict, []), + subst_macros_rev([Paren | Toks], MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks); + ParamsAndBody -> + %% This macro does have vars. + %% Collect all of the passe arguments, in an ordered list + {NToks, Arguments} = subst_macros_get_args(Toks, []), + %% Expand the varibles + ExpandedParamsToks = subst_macros_subst_args_for_vars(ParamsAndBody, Arguments), + %% Recursively expand any macro calls inside this macro's value + %% TODO: avoid infinite expansion due to circular references (even indirect ones) + RevExpandedOtherMacrosToks = subst_macros_rev(ExpandedParamsToks, MacroDict, []), + subst_macros_rev(NToks, MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks) + end; + +subst_macros_rev([{'?', _}, {_, _, Name} | Toks], MacroDict, RevOutToks) -> + %% This macro invocation does not have arguments. + %% Therefore the definition should not have parameters + {[], MacroValue} = dict:fetch(Name, MacroDict), + + %% Recursively expand any macro calls inside this macro's value + %% TODO: avoid infinite expansion due to circular references (even indirect ones) + RevExpandedOtherMacrosToks = subst_macros_rev(MacroValue, MacroDict, []), + subst_macros_rev(Toks, MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks); + +subst_macros_rev([Tok | Toks], MacroDict, RevOutToks) -> + subst_macros_rev(Toks, MacroDict, [Tok | RevOutToks]); +subst_macros_rev([], _MacroDict, RevOutToks) -> RevOutToks. + +subst_macros_get_args([{')', _} | Toks], RevArgs) -> + {Toks, reverse(RevArgs)}; +subst_macros_get_args([{',', _}, {var, _, ArgName} | Toks], RevArgs) -> + subst_macros_get_args(Toks, [ArgName | RevArgs]); +subst_macros_get_args([{var, _, ArgName} | Toks], RevArgs) -> + subst_macros_get_args(Toks, [ArgName | RevArgs]). + +subst_macros_subst_args_for_vars({[], BodyToks}, []) -> + BodyToks; +subst_macros_subst_args_for_vars({[Param | Params], BodyToks}, [Arg | Args]) -> + NBodyToks = keyreplace(Param, 3, BodyToks, {var, 1, Arg}), + subst_macros_subst_args_for_vars({Params, NBodyToks}, Args). + +read_include_file(Filename, IncludeSearchPath) -> + case file:path_open(IncludeSearchPath, Filename, [read, raw, binary]) of + {ok, IoDevice, FullName} -> + {ok, Data} = file:read(IoDevice, filelib:file_size(FullName)), + file:close(IoDevice), + binary_to_list(Data); + {error, Reason} -> + throw({failed_to_read_include_file, Reason, Filename, IncludeSearchPath}) + end. + +normalize_record({attribute, La, record, {Record, Fields}} = Form) -> + case epp:normalize_typed_record_fields(Fields) of + {typed, NewFields} -> + {{attribute, La, record, {Record, NewFields}}, + [{attribute, La, type, + {{record, Record}, Fields, []}}]}; + not_typed -> + {Form, []} + end; +normalize_record(Form) -> + {Form, []}. \ No newline at end of file diff --git a/src/dynamicCompile/dynamic_kvs.erl b/src/dynamicCompile/dynamic_kvs.erl new file mode 100644 index 0000000..c24e806 --- /dev/null +++ b/src/dynamicCompile/dynamic_kvs.erl @@ -0,0 +1,85 @@ +-module(dynamic_kvs). + +-define(boot_type_supervisor, supervisor). +-define(boot_type_worker, worker). +-define(boot_type_simple_worker, simple_worker). +-type boot_type() :: ?boot_type_simple_worker | ?boot_type_supervisor | ?boot_type_worker. +-define(MATH_INT32_MAX, 4294967296). + +-record(boot, {module :: atom(), type :: boot_type(), hasname = true :: boolean(), params :: any()}). + +-export([start/1, start/0, get_name/1, init/1, handle_call/3, new/1, new/2]). +%% API +-export([set/3]). + +-record(dynamic_kvs, { + modules = [] :: [{Mod :: module(), [{Key :: atom(), Val :: any()}]}] +}). + +start(FatherPID) -> + boot:start_child(FatherPID, #boot{module = ?MODULE, params = {}, type = ?boot_type_worker}). + +start() -> + boot:start(#boot{module = ?MODULE, params = {}, type = ?boot_type_worker}). + +get_name(_) -> + ?MODULE. + +init(_) -> + {ok, #dynamic_kvs{modules = []}}. + +new(ModuleName) -> + new(ModuleName, []). + +new(ModuleName, Kvs) -> + boot:call(?MODULE, {new, ModuleName, Kvs}). + +set(ModuleName, Key, Val) -> + boot:call(?MODULE, {set, ModuleName, Key, Val}). + +handle_call({new, ModuleName, Kvs}, _, DynamicKvs) -> + gen(ModuleName, Kvs), + NewDynamicKvs = + case lists:keyfind(ModuleName, 1, DynamicKvs#dynamic_kvs.modules) of + false -> + DynamicKvs#dynamic_kvs{modules = [{ModuleName, Kvs} | DynamicKvs#dynamic_kvs.modules]}; + _ -> + DynamicKvs + end, + {reply, ok, NewDynamicKvs}; + +handle_call({set, ModuleName, Key, Val}, _, DynamicKvs) -> + case lists:keytake(ModuleName, 1, DynamicKvs#dynamic_kvs.modules) of + false -> + {reply, {error, not_find_module}, DynamicKvs}; + {value, {_, Kvs}, RemainMods} -> + NewKvs = com_lists:keyreplace(Key, 1, Kvs, {Key, Val}), + gen(ModuleName, NewKvs), + {reply, ok, DynamicKvs#dynamic_kvs{modules = [{ModuleName, NewKvs} | RemainMods]}} + end. + + +concat(List) -> + lists:concat(List). + +gen(ModuleName, Kvs) -> + Meta = meta:new(ModuleName), + Fun = + fun({K, V}, MetaTemp) -> + FunStr = concat([K, "() ->\n\t", com_util:term_to_string(V), ".\n"]), + case meta:add_func(MetaTemp, FunStr) of + {ok, MetaTemp2} -> MetaTemp2; + Error -> + io:format("module(~p) define func(~s) error ~p~n", [ModuleName, FunStr, Error]), + exit(Error) + end + end, + FinalMeta = lists:foldl(Fun, Meta, Kvs), + FinalMeta2 = meta:add_func(FinalMeta, concat(["set(Key, Val) ->\n\tdynamic_kvs:set(", ModuleName, ", Key, Val).\n"])), + case meta:compile(FinalMeta2) of + {error, Error} -> + io:format("error ~p", [Error]), + ok; + ok -> + ok + end. \ No newline at end of file diff --git a/src/dynamicCompile/meta.erl b/src/dynamicCompile/meta.erl new file mode 100644 index 0000000..cec5e25 --- /dev/null +++ b/src/dynamicCompile/meta.erl @@ -0,0 +1,1136 @@ +%% @author Yariv Sadan [http://yarivsblog.com] +%% @copyright Yariv Sadan 2006-2007 +%% @modify by Lili 2013 +%% +%% @doc meta: Simple Metaprogramming for Erlang +%% +%% meta is an Erlang library +%% that simplifies the creation and manipulation of Erlang modules in +%% runtime. +%% +%% You don't need to know meta in order to use ErlyWeb; meta +%% is included in ErlyWeb because ErlyWeb uses it internally. +%% +%% meta uses Erlang's capabilities for hot code swapping and +%% abstract syntax tree transformations to do its magic. meta is inspired by +%% the rdbms_codegen.erl module in the RDBMS application written by +%% Ulf Wiger. RDBMS is part of Jungerl ([http://jungerl.sf.net]). +%% +%% Here's a quick example illustrating how to use meta: +%% ``` +%% test_meta() -> +%% M1 = meta:new(foo), +%% {ok, M2} = meta:add_func(M1, "bar() -> 1 + 1."), +%% meta:compile(M2), +%% foo:bar(), % returns 2`` +%% meta:has_func(M2, bar, 0). % returns true +%% ''' +%% +%% New functions can be expressed either as strings of Erlang code +%% or as abstract forms. For more information, read the Abstract Format +%% section in the ERTS User's guide +%% ([http://erlang.org/doc/doc-5.5/erts-5.5/doc/html/absform.html#4]). +%% +%% Using the abstract format, the 3rd line of the above example +%% would be written as +%% ``` +%% {ok,M2} = meta:add_func(M1, {function,1,bar,0, +%% [{clause,1,[],[], +%% [{op,1,'+',{integer,1,1},{integer,1,1}}]}]). +%% ''' +%% +%%

The abstact format may look more verbose in this example, but +%% it's also easier to manipulate in code.

+%% + +%% Copyright (c) Yariv Sadan 2006-2007 +%% +%% Permission is hereby granted, free of charge, to any person +%% obtaining a copy of this software and associated documentation +%% files (the "Software"), to deal in the Software without restriction, +%% including without limitation the rights to use, copy, modify, merge, +%% publish, distribute, sublicense, and/or sell copies of the Software, +%% and to permit persons to whom the Software is furnished to do +%% so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included +%% in all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +%% IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +%% CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +%% TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +%% SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +-module(meta). +-author("Yariv Sadan (yarivsblog@gmail.com, http://yarivsblog.com"). +-export([new/1, + new_from_forms/1, + new_from_src/1, + new_from_module/1, + new_from_module/2, + new_from_module/3, + new_from_file/1, + new_from_file/2, + new_from_file/3, + get_module/1, + get_modulename/1, + set_module/2, + get_forms/1, + set_forms/2, + get_exports/1, + set_exports/2, + get_export_all/1, + set_export_all/2, + remove_export/3, + get_attribute/2, + add_inc/2, + add_func/2, + add_func/3, + add_funcs/3, + remove_func/3, + is_has_func/4, + is_has_func/3, + is_export/3, + get_all_func/1, + get_func/3, + %%get_func_src/3, + replace_func/2, + compile/1, + compile/2, + rename/2, + curry/2, + curry/4, + curry/5, + curry_add/3, + curry_add/4, + curry_add/5, + curry_add/6, + curry_replace/3, + curry_replace/4, + embed_params/2, + embed_params/4, + embed_params/5, + embed_all/2, + extend/2, + extend/3, + extend/4, + asb_to_src/1, + beam_to_src/1, + to_src/1, + to_src/2, + to_forms/1 +]). + +-export([get_records_name/1, + get_record/2]). + +-define(L(Obj), io:format("LOG ~s ~w ~p\n", [?FILE, ?LINE, Obj])). +-define(S(Obj), io:format("LOG ~s ~w ~s\n", [?FILE, ?LINE, Obj])). +-include("meta.hrl"). +-include_lib("kernel/include/file.hrl"). + +%% for a module. +%%hThe abstract form for the function, as described +-type func_form() :: term(). + +%% The record type holding the abstract representation for a module. + +-type meta_mod() :: #meta_mod{}. + +%% @doc Create a new meta_mod for a module with the given name. +-spec new(Module :: atom()) -> meta_mod(). +new(ModuleName) when is_atom(ModuleName) -> + #meta_mod{module = ModuleName}. + + +%% @doc build a meta_mod from forms. +-spec new_from_forms([erl_parse:abstract_form()]) -> + {ok, meta_mod()} | {error, {invalid_module, term()}}. +new_from_forms([{attribute, _, file, {FileName, _FileNum}}, + {attribute, _, module, ModuleName} | Forms]) -> + Mod = + lists:foldr(fun(Form, MateMode) -> + insert_form(Form, MateMode) + end, + #meta_mod{module = ModuleName, + file = FileName}, + Forms), + {ok, Mod}; +new_from_forms(Mod) -> + {error, {invalid_module, Mod}}. + +insert_form({attribute, _, export, ExportList}, #meta_mod{exports = Exports} = Mod) -> + Mod#meta_mod{exports = ExportList ++ Exports}; + +insert_form({attribute, _, compile, export_all}, #meta_mod{export_all = false} = Mod) -> + Mod#meta_mod{export_all = true}; + +insert_form({attribute, _Line, record, _} = RecordForm, #meta_mod{records = Records} = Mod) -> + Mod#meta_mod{records = [RecordForm | Records]}; + +insert_form({attribute, _Line, _Other, _} = AttributeForm, #meta_mod{attributes = Attributes} = Mod) -> + Mod#meta_mod{attributes = [AttributeForm | Attributes]}; + +insert_form({eof, _} = Eof, Mod) -> + Mod#meta_mod{eof = Eof}; + +insert_form(From, #meta_mod{forms = Forms} = Mod) -> + Mod#meta_mod{forms = [From | Forms]}. + + +new_from_src(Src) -> + Forms = dynamic_compile:get_forms_from_src(Src), + new_from_forms(Forms). + + +%% @equiv new_from_module(ModuleName, []) +new_from_module(ModuleName) -> + new_from_module(ModuleName, []). + +%% @equiv new_from_module(ModuleName, IncludePaths, []) +new_from_module(ModuleName, IncludePaths) -> + new_from_module(ModuleName, IncludePaths, []). + +%% @doc Create a meta_mod tuple for an existing module. If ModuleName is a +%% string, it is interpreted as a file name (this is the same as calling +%% @{link meta:new_from_file}). If ModuleName is an atom, meta attempts to +%% find its abstract represtation either from its source file or from +%% its .beam file directly (if it has been compiled with debug_info). +%% If the abstract representation can't be found, this function returns +%% an error. +%% +%% The IncludePaths parameter is used when 'ModuleName' is a file name. +%% +-spec new_from_module(ModuleName :: atom() | string(), IncludePaths :: [string()], + Macros :: [{atom(), term()}]) -> + {ok, meta_mod()} | {error, {invalid_module, atom()}}. + +new_from_module(ModuleName, IncludePaths, Macros) when is_list(ModuleName) -> + new_from_file(ModuleName, IncludePaths, Macros); +new_from_module(ModuleName, IncludePaths, Macros) when is_atom(ModuleName) -> + [_Exports, _Imports, _Attributes, + {compile, [_Options, _Version, _Time, {source, Path}]}] = + ModuleName:module_info(), + case new_from_file(Path, IncludePaths, Macros) of + {ok, _Mod} = Res -> + Res; + _ -> + case code:which(ModuleName) of + Path1 when is_list(Path1) -> + case get_forms(ModuleName, Path1) of + {ok, Forms} -> + new_from_forms(Forms); + _Other -> + {error, {invalid_module, ModuleName}} + end; + _Err -> + {error, {invalid_module, ModuleName}} + end + end. + +%% @equiv new_from_file(SrcFilePath, []) +new_from_file(SrcFilePath) -> + new_from_file(SrcFilePath, []). + +% @equiv new_from_file(SrcFilePath, IncludePaths, []) +new_from_file(SrcFilePath, IncludePaths) -> + new_from_file(SrcFilePath, IncludePaths, []). + +%% @doc Create a meta_mod for a module from its source file. +%% +-spec new_from_file(SrcFilePath :: string(), + IncludePaths :: [string()], + Macros :: [{atom(), term()}]) -> + {ok, meta_mod()} | {error, {invalid_module, term()}}. +new_from_file(SrcFilePath, IncludePaths, Macros) -> + case epp:parse_file(SrcFilePath, [filename:dirname(SrcFilePath) | + IncludePaths], Macros) of + {ok, Forms} -> + new_from_forms(Forms); + _err -> + {error, {invalid_module, SrcFilePath}} + end. + +-compile({inline, {get_module, 1}}). +%% @doc Return the module name for the meta_mod. +-spec get_module(meta_mod()) -> atom(). +get_module(MetaMod) -> MetaMod#meta_mod.module. + +get_modulename(MetaMod) -> + atom_to_list(get_module(MetaMod)). + +%% @doc Set the meta_mod's module name. +-spec set_module(MetaMod :: meta_mod(), NewName :: atom()) -> NewMod :: meta_mod(). +set_module(MetaMod, NewName) -> + MetaMod#meta_mod{module = NewName}. + +%% @doc Return the list of function forms in the meta_mod. +-spec get_forms(MetaMod :: meta_mod()) -> [term()]. +get_forms(MetaMod) -> + MetaMod#meta_mod.forms. + +set_forms(MetaMod, Forms) -> + MetaMod#meta_mod{forms = Forms}. + +%% @doc Return the list of exports in the meta_mod. +%% +-spec get_exports(MetaMod :: meta_mod()) -> [{FuncName :: atom(), Arity :: integer()}]. +get_exports(MetaMod) -> + case MetaMod#meta_mod.export_all of + false -> + MetaMod#meta_mod.exports; + true -> + lists:foldl( + fun({function, _L, Name, Arity, _Clauses}, Exports) -> + [{Name, Arity} | Exports]; + (_Form, Exports) -> + Exports + end, [], MetaMod#meta_mod.forms) + end. + + +%% @doc Set the meta_mod's export list to the new list. +%% +-spec set_exports(MetaMod :: meta_mod(), + Exports :: [{FuncName :: atom(), Arity :: integer()}]) -> NewMod :: meta_mod(). +set_exports(MetaMod, Exports) -> + MetaMod#meta_mod{exports = Exports}. + +%% @doc Get the export_all value for the module. +%% +%% @spec get_export_all(MetaMod::meta_mod) -> true | false +get_export_all(MetaMod) -> + MetaMod#meta_mod.export_all. + +%% @doc Set the export_all value for the module. +%% +-spec set_export_all(MetaMod :: meta_mod(), Val :: true | false) -> meta_mod(). +set_export_all(MetaMod, Val) + when Val =:= true; + Val =:= false -> + MetaMod#meta_mod{export_all = Val}. + +%% @doc Remove the export from the list of exports in the meta_mod. +%% +%% @spec remove_export(MetaMod::meta_mod(), FuncName::atom(), +%% Arity::integer()) -> NewMod::meta_mod() +remove_export(MetaMod, FuncName, Arity) -> + MetaMod#meta_mod{exports = + lists:delete({FuncName, Arity}, + MetaMod#meta_mod.exports)}. + +%% @doc Get the value a the module's attribute. +-spec get_attribute(meta_mod(), AttName :: atom()) -> [term()]. +get_attribute(MetaMod, AttName) -> + lists:foldl( + fun({attribute, _, Name, Val}, AccIn) when Name =:= AttName -> + [Val | AccIn]; + ({attribute, _, _, _}, AccIn) -> + AccIn + end, + [], + MetaMod#meta_mod.attributes). + + +%% Get the abstract representation, if available, for the module. +%% +%% Strategy: +%% 1) Try to get the abstract code from the module if it's compiled +%% with debug_info. +%% 2) Look for the source file in the beam file's directory. +%% 3) If the file's directory ends with 'ebin', then search in +%% [beamdir]/../src +get_forms(Module, Path) -> + case beam_lib:chunks(Path, [abstract_code]) of + {ok, {_, [{abstract_code, {raw_abstract_v1, Forms}}]}} -> + {ok, Forms}; + _Err -> + case filename:find_src(Module, [{"ebin", "src"}]) of + {error, _} = Err -> + get_forms_from_binary(Module, Err); + {SrcPath, _} -> + Filename = SrcPath ++ ".erl", + epp:parse_file(Filename, [filename:dirname(Filename)], []) + end + end. + +get_dirs_in_dir(Dir) -> + case file:list_dir(Dir) of + {error, _} -> + undefined; + {ok, Listing} -> + lists:foldl( + fun(Name, Acc) -> + Path = Dir ++ "/" ++ Name, + case file:read_file_info(Path) of + {ok, #file_info{type = directory}} -> [Path | Acc]; + _ -> Acc + end + end, [], Listing) + end. + +%% @doc Try to infer module source files from the beam code path. +get_forms_from_binary(Module, OrigErr) -> + Ret = + case code:where_is_file(atom_to_list(Module) ++ ".beam") of + non_existing -> + OrigErr; + Filename -> + %% We could automatically obtain a list of all dirs under this dir, but we just do + %% a hack for now. + Basedir = filename:dirname(Filename), + Lastdir = filename:basename(Basedir), + if Lastdir == "ebin" -> + Rootdir = filename:dirname(Basedir), + DirList = [Rootdir ++ "/src"], + get_forms_from_file_list(Module, Rootdir, + DirList ++ get_dirs_in_dir(Rootdir ++ "/src")); + true -> + DirList = [Basedir], + get_forms_from_file_list(Module, Basedir, DirList) + end + end, + if Ret == [] -> OrigErr; + true -> Ret + end. +get_forms_from_file_list(_Module, _Basedir, []) -> + []; +get_forms_from_file_list(Module, Basedir, [H | T]) -> + Filename = H ++ "/" ++ atom_to_list(Module) ++ ".erl", + case file:read_file_info(Filename) of + {ok, #file_info{type = regular}} -> + epp:parse_file(Filename, [filename:dirname(Filename)], []); + _ -> + get_forms_from_file_list(Module, Basedir, T) + end. + +add_inc(MetaMod, Inc) -> + {ok, MetaMod#meta_mod{includes = [Inc | MetaMod#meta_mod.includes]}}. + +%% @doc Add a new function to the meta_mod and return the resulting meta_mod. +%% This function calls add_func(MetaMod, Form, true). +-spec add_func(meta_mod(), func_form() | string()) -> + {ok, NewMod :: meta_mod()} | {error, parse_error}. +add_func(MetaMod, Form) -> + add_func(MetaMod, Form, true). + +%% @doc Add a new function to the meta_mod and return the new MetaMod +%% record. Export is a boolean variable indicating if the function should +%% be added to the module's exports. + +-spec add_func(MetaMod :: meta_mod(), + Func :: func_form() | string(), + Export :: boolean()) -> + {ok, NewMod :: meta_mod()} | {error, parse_error}. +add_func(MetaMod, Func, Export) when is_list(Func) -> + case parse_func_string(Func) of + {ok, Form} -> + add_func(MetaMod, Form, Export); + Err -> + Err + end; +add_func(MetaMod, {function, _Line, FuncName, Arity, _Clauses} = Form, true) -> + Foo = + {ok, MetaMod#meta_mod{ + exports = [{FuncName, Arity} | MetaMod#meta_mod.exports], + forms = [Form | MetaMod#meta_mod.forms] + }}, + Foo; +add_func(MetaMod, {function, _Line, _FuncName, _Arity, _Clauses} = Form, false) -> + {ok, MetaMod#meta_mod{forms = [Form | MetaMod#meta_mod.forms]}}; + +add_func(_, _, _) -> + {error, parse_error}. + +add_funcs(MetaMod, Funs, Export) -> + Fun = + fun(Function, Meta) -> + case add_func(Meta, Function, Export) of + {error, _, R} -> + exit({function, R, Function}); + {ok, New} -> + New + end + end, + {ok, lists:foldl(Fun, MetaMod, Funs)}. + + +parse_func_string(Func) -> + case erl_scan:string(Func) of + {ok, Toks, _} -> + case erl_parse:parse_form(Toks) of + {ok, _Form} = Res -> + Res; + _Err -> + {error, parse_error, _Err} + end; + _Err -> + {error, parse_error, _Err} + end. + +%% @doc Try to remove the function from the meta_mod. +%% If the function exists, the new meta_mod is returned. Otherwise, +%% original meta_mod is returned. +%% +-spec remove_func(MetaMod :: meta_mod(), FuncName :: string(), Arity :: integer()) + -> NewMod :: meta_mod(). +remove_func(MetaMod, FuncName, Arity) -> + MetaMod#meta_mod{forms = + lists:filter( + fun({function, _Line, FuncName1, Arity1, _Clauses}) + when FuncName1 =:= FuncName, Arity =:= Arity1 -> + false; + (_) -> + true + end, MetaMod#meta_mod.forms), + exports = + lists:filter( + fun({FuncName1, Arity1}) + when FuncName1 =:= FuncName, + Arity1 =:= Arity -> + false; + (_) -> + true + end, MetaMod#meta_mod.exports) + }. + +is_export(#meta_mod{exports = Exports}, FuncName, Arity) -> + lists:member({FuncName, Arity}, Exports). + + +%% @doc Check whether the meta_mod has a function with the given name +%% and arity. +-spec is_has_func(MetaMod :: meta_mod(), FuncName :: atom(), Arity :: integer()) -> boolean(). +is_has_func(MetaMod, FuncName, Arity) -> + lists:any(fun({function, _Line, FuncName1, Arity1, _Clauses}) + when FuncName1 == FuncName, Arity1 == Arity -> + true; + (_) -> + false + end, MetaMod#meta_mod.forms). + +is_has_func(MetaMod, FuncName, Arity, true) -> + is_has_func(MetaMod, FuncName, Arity) andalso is_export(MetaMod, FuncName, Arity); +is_has_func(MetaMod, FuncName, Arity, false) -> + is_has_func(MetaMod, FuncName, Arity). + + +get_all_func(#meta_mod{forms = Forms}) -> + Forms. + + +%% @doc Get the form for the function with the specified arity in the +%% meta_mod. +%% +%% @spec get_func(MetaMod::meta_mod() | atom(), +%% FuncName::atom(), Arity::integer()) -> +%% {ok, func_form()} | {error, Err} +get_func(Module, FuncName, Arity) when is_atom(Module) -> + case meta:new_from_module(Module) of + {ok, C1} -> + get_func(C1, FuncName, Arity); + Err -> + Err + end; +get_func(MetaMod, FuncName, Arity) when is_record(MetaMod, meta_mod) -> + get_func2(MetaMod#meta_mod.forms, FuncName, Arity). + +get_func2([], FuncName, Arity) -> + {error, {function_not_found, {FuncName, Arity}}}; +get_func2([{function, _Line, FuncName, Arity, _Clauses} = Form | _Rest], + FuncName, Arity) -> + {ok, Form}; +get_func2([_Form | Rest], FuncName, Arity) -> + get_func2(Rest, FuncName, Arity). + +%%get_func_src(MetaMod, FuncName, Arity) -> when is_atom(Module) -> +%%Asb = get_func(Meta, FuncName, Arity), +%%asb_to_src([Asb]). + +%% @doc +%% Replace an existing function with the new one. If the function doesn't exist +%% the new function is added to the meta_mod. +%% This is tantamount to calling meta:remove_func followed by meta:add_func. +%% + + + +-spec replace_func(MetaMod :: meta_mod(), Function :: string() | func_form()) -> + {ok, NewMod :: meta_mod()} | {error, Error :: term()}. +replace_func(MetaMod, Function) when is_list(Function) -> + case parse_func_string(Function) of + {ok, Form} -> + replace_func(MetaMod, Form); + Err -> + io:format("-------> ~p~n", [Err]), + Err + end; +replace_func(MetaMod, {function, _Line, FuncName, Arity, _Clauses} = Form) -> + Mod1 = remove_func(MetaMod, FuncName, Arity), + add_func(Mod1, Form); +replace_func(_MetaMod, _) -> + {error, parse_error}. + + +%% @doc transform metaMod to Forms. +%% +to_forms(#meta_mod{module = Module, + file = File, + includes = Includes, + records = Records, + attributes = Attributes, + forms = Froms, + eof = Eof} = MetaMod) -> + FileName = + case File of + undefined -> atom_to_list(get_module(MetaMod)); + Val -> Val + end, + ExtRecords = extrecords(Includes), + FormsFile = {attribute, 1, file, {FileName, 1}}, + FormModule = {attribute, 2, module, Module}, + FormExports = {attribute, 3, export, get_exports(MetaMod)}, + + FormsBody1 = + lists:foldr(fun(Form, AccIn) -> [Form | AccIn] end, + [Eof], + Froms), + FormsBody2 = + lists:foldr(fun(Form, AccIn) -> [Form | AccIn] end, + FormsBody1, + Attributes), + + FormsBody3 = + lists:foldr(fun(Form, AccIn) -> [Form | AccIn] end, + FormsBody2, + Records ++ ExtRecords), + + [FormsFile, FormModule, FormExports | FormsBody3]. + +extrecords(Includes) -> + Fun = + fun(Include, Records) -> + {ok, EppForms} = epp:parse_file(Include, []), + filter_record(EppForms) ++ Records + end, + lists:foldl(Fun, [], Includes). + +filter_record(EppForms) -> + Fun = fun(#attribute{name = record}) -> true; (_) -> false end, + lists:filter(Fun, EppForms). + +%% @doc Compile the module represented by the meta_mod and load the +%% resulting BEAM into the emulator. This function calls +%% compile(MetaMod, [report_errors, report_warnings]). +%% +-spec compile(MetaMod :: meta_mod()) -> ok | {error, term()}. +compile({ok, MetaMod}) -> + compile(MetaMod); +compile(MetaMod) -> + compile(MetaMod, [report_errors, report_warnings, + return_errors]). + +%% @doc Compile the module represented by the meta_mod and load the +%% resulting BEAM into the emulator. 'Options' is a list of options as +%% described in the 'compile' module in the Erlang documentation. +%% +%% If the 'outdir' option is provided, +%% the .beam file is written to the destination directory. +%% +-spec compile(MetaMod :: meta_mod(), Options :: [term()]) -> ok | {error, term()}. +compile(#meta_mod{} = MetaMod, Options) -> + case catch compile:forms(to_forms(MetaMod), Options) of + {ok, Module, Bin} -> + Res = + case lists:keysearch(outdir, 1, Options) of + {value, {outdir, OutDir}} -> + file:write_file(OutDir ++ ['/' | atom_to_list(MetaMod#meta_mod.module)] ++ ".beam", + Bin); + false -> ok + end, + io:format("Module ~p Res ~p ~n", [Module, Res]), + case Res of + ok -> + io:format("Module2 ~p ~n", [Module]), + catch code:purge(Module), + io:format("Module3 ~p ~n", [Module]), + %%case code:load_binary(Module, atom_to_list(Module) ++ ".erl", Bin) of %% Fix Xref bug + case catch code:load_binary(Module, atom_to_list(Module), Bin) of + {module, _Module} -> + ok; + Err -> + Err + end; + Err -> + Err + end; + Err -> + Err + end; +compile(NotMetaMod, Options) -> + io:format("compile ~p ~p~n", [NotMetaMod, Options]). + +%% @doc Change the name of the function represented by the form. +%% +%% @spec rename(Form::func_form(), NewName::atom()) -> +%% {ok, NewForm::func_form()} | {error, Err} +rename({function, Line, _Name, Arity, Clauses}, NewName) -> + {function, Line, NewName, Arity, Clauses}. + + +%% @doc Get the curried form for the function and parameter(s). Currying +%% involves replacing one or more of the function's leading parameters +%% with predefined values. +%% +%% @spec curry(Form::func_form(), Param::term() | [term()]) -> +%% {ok, NewForm::func_form()} | {error, Err} +curry(Form, Param) when not is_list(Param) -> + curry(Form, [Param]); +curry({function, _Line, _Name, Arity, _Clauses}, Params) + when length(Params) > Arity -> + {error, too_many_params}; +curry({function, Line, Name, Arity, Clauses}, NewParams) -> + NewClauses = + lists:foldl( + fun(Clause, Clauses1) -> + [curry_clause(Clause, NewParams) | Clauses1] + end, [], Clauses), + {ok, {function, Line, Name, Arity - length(NewParams), NewClauses}}. + +curry_clause({clause, L1, ExistingParams, Guards, _Exprs} = Clause, + NewParams) -> + {FirstParams, LastParams} = + lists:split(length(NewParams), ExistingParams), + %% Matches = + %% lists:foldl( + %% fun({Var, NewVal}, Acc) -> + %% [{match, 1, Var, erl_parse:abstract(NewVal)} | Acc] + %% end, [], lists:zip(FirstParams, NewParams)), + %% {clause, L1, LastParams, Guards, Matches ++ Exprs}. + + Vals = + lists:foldl( + fun({{var, _, Name}, NewVal}, Acc) -> + [{Name, erl_parse:abstract(NewVal)} | Acc]; + (_, Acc) -> + Acc + end, [], lists:zip(FirstParams, NewParams)), + + NewExprs = replace_vars(Clause, Vals), + + {clause, L1, LastParams, Guards, NewExprs}. + +replace_vars(Clause, Vals) -> + Tree = + erl_syntax_lib:map( + fun({var, _L2, Name} = Expr) -> + case proplists:lookup(Name, Vals) of + none -> + Expr; + {_, Val} -> + Val + end; + (Expr) -> + Expr + end, Clause), + {clause, _, _, _, NewExprs} = erl_syntax:revert(Tree), + NewExprs. + + +%% @doc Curry the function from the module with the given param(s) +%% +%% @spec curry(ModName::atom(), Name::atom(), Arity::integer(), +%% Params::term() | [term()]) -> +%% {ok, NewForm} | {error, Err} +curry(ModName, Name, Arity, Params) when is_atom(ModName) -> + case new_from_module(ModName) of + {ok, MetaMod} -> + curry(MetaMod, Name, Arity, Params); + Err -> + Err + end; + +%% @doc Curry the function from the meta_mod with +%% the given param(s) +%% +%% @spec curry(MetaMod::meta_mod(), Name::atom(), arity::integer(), +%% Params::term() | [terms()]) -> +%% {ok, NewForm} | {error, Err} +curry(MetaMod, Name, Arity, Params) -> + case get_func(MetaMod, Name, Arity) of + {ok, Form} -> + curry(Form, Params); + Err -> + Err + end. + + +%% @doc Curry the function from the module or meta_mod +%% with the param(s), and return its renamed form. +%% +%% @spec curry(Module::atom() | meta_mod(), Name::atom(), Arity::integer(), +%% Params::term() | [terms()], NewName::atom()) -> +%% {ok, NewForm} | {error, Err} +curry(Module, Name, Arity, Params, NewName) -> + case curry(Module, Name, Arity, Params) of + {ok, NewForm} -> + {ok, rename(NewForm, NewName)}; + Err -> + Err + end. + + +%% @doc Add the curried form of the function in the meta_mod +%% with its curried form. +%% +%% @spec curry_add(MetaMod::meta_mod(), Form::func_form(), +%% Params::term() | [term()]) -> +%% {ok, NewMetaMod::meta_mod()} | {error, Err} +curry_add(MetaMod, {function, _Line, Name, Arity, _Clauses}, Params) -> + curry_add(MetaMod, Name, Arity, Params). + +%% @doc Add the curried form of the function +%% in the meta_mod with its curried form. +%% +%% @spec curry_add(MetaMod::meta_mod(), Name::atom(), Arity::integer(), +%% Params::term() | [term()]) -> +%% {ok, NewMetaMod::meta_mod()} | {error, Err} +curry_add(MetaMod, Name, Arity, Params) -> + curry_change(MetaMod, Name, Arity, Params, false). + +%% @doc Curry the function form from the meta_mod, then add it +%% to the other meta_mod with a new name. +%% +%% @spec curry_add(MetaMod::meta_mod(), Name::atom(), Arity::integer(), +%% Params::[term()], NewName::atom()) -> {ok, NewMod::meta_mod()} | +%% {error, Err} +curry_add(MetaMod, Name, Arity, Params, NewName) -> + curry_add(MetaMod, MetaMod, Name, Arity, Params, NewName). + +%% @doc Curry the function in the module, rename the curried form, and +%% add it to the meta_mod. +%% +%% @spec curry_add(MetaMod::meta_mod(), Module::atom() | meta_mod(), +%% Name::atom(), Arity::integer(), Params::term() | [term()], +%% NewName::atom()) -> +%% {ok, NewMod::meta_mod()} | {error, Error} +curry_add(MetaMod, Module, Name, Arity, Params, NewName) -> + case curry(Module, Name, Arity, Params, NewName) of + {ok, Form} -> + add_func(MetaMod, Form); + Err -> + Err + end. + + +curry_change(MetaMod, Name, Arity, Params, Remove) -> + case get_func(MetaMod, Name, Arity) of + {ok, OldForm} -> + case curry(OldForm, Params) of + {ok, NewForm} -> + MetaMod1 = + case Remove of + true -> + remove_func(MetaMod, Name, Arity); + false -> + MetaMod + end, + add_func(MetaMod1, NewForm); + Err -> + Err + end; + Err -> + Err + end. + +%% @doc Replace the function in the meta_mod with +%% its curried form. +%% +%% @spec curry_replace(MetaMod::meta_mod(), Form::func_form(), +%% Params::term() | [term()]) -> +%% {ok, NewMetaMod::meta_mod()} | {error, Err} +curry_replace(MetaMod, {function, _Line, Name, Arity, _Clauses}, Params) -> + curry_replace(MetaMod, Name, Arity, Params). + + +%% @doc Replace the function in the meta_mod with +%% its curried form. +%% +%% @spec curry_replace(MetaMod::meta_mod(), Name::atom(), +%% Arity::integer(), Params::term() | list()) -> +%% {ok, NewMetaMod::meta_mod()} | {error, Err} +curry_replace(MetaMod, Name, Arity, Params) -> + curry_change(MetaMod, Name, Arity, Params, true). + + +%% @doc This function takes a function form and list of name/value pairs, +%% and replaces all the function's parameters that whose names match an +%% element from the list with the predefined value. +%% +%% @spec embed_params(Func::func_form(), +%% Vals::[{Name::atom(), Value::term()}]) -> NewForm::func_form() +embed_params({function, L, Name, Arity, Clauses}, Vals) -> + NewClauses = + lists:map( + fun({clause, L1, Params, Guards, _Exprs} = Clause) -> + {EmbeddedVals, OtherParams} = + lists:foldr( + fun({var, _, VarName} = Param, {Embedded, Rest}) -> + case proplists:lookup(VarName, Vals) of + none -> + {Embedded, [Param | Rest]}; + {_, Val} -> + {[{VarName, erl_parse:abstract(Val)} | + Embedded], Rest} + end; + (Param, {Embedded, Rest}) -> + {Embedded, [Param | Rest]} + end, {[], []}, Params), + NewExprs = replace_vars(Clause, EmbeddedVals), + {clause, L1, OtherParams, Guards, NewExprs} + + + %% {Params1, Matches1, _RemainingVals} = + %% lists:foldl( + %% fun({var, _L2, ParamName} = Param, + %% {Params2, Matches2, Vals1}) -> + %% case lists:keysearch(ParamName, 1, Vals1) of + %% {value, {_Name, Val} = Elem} -> + %% Match = {match, L1, Param, + %% erl_parse:abstract(Val)}, + %% {Params2, [Match | Matches2], + %% lists:delete(Elem, Vals1)}; + %% false -> + %% {[Param | Params2], Matches2, Vals1} + %% end; + %% (Param, {Params2, Matches2, Vals1}) -> + %% {[Param | Params2], Matches2, Vals1} + %% end, {[], [], Vals}, Params), + %% [{clause, L1, lists:reverse(Params1), Guards, + %% lists:reverse(Matches1) ++ Exprs} | Clauses1] + end, Clauses), + NewArity = + case NewClauses of + [{clause, _L2, Params, _Guards, _Exprs} | _] -> + length(Params); + _ -> + Arity + end, + {function, L, Name, NewArity, NewClauses}. + +%% @doc Apply {@link embed_params/2} to a function from the meta_mod and +%% add the resulting function to the meta_mod, and return the resulting +%% meta_mod. +%% +%% @spec embed_params(MetaMod::meta_mod(), Name::atom(), Arity::integer(), +%% Values::proplist()) -> {ok, NewMetaMod::meta_mod()} | {error, Err} +embed_params(MetaMod, Name, Arity, Values) -> + embed_params(MetaMod, Name, Arity, Values, Name). + +%% @doc Apply embed_params/2 to the function from the meta_mod and +%% add the resulting function to the meta_mod after renaming the function. +%% +%% @spec embed_params(MetaMod::meta_mod(), Name::atom(), Arity::integer(), +%% Values::proplist(), NewName::atom()) -> +%% {ok, NewMetaMod::meta_mod()} | {error, Err} +embed_params(MetaMod, Name, Arity, Values, NewName) -> + case get_func(MetaMod, Name, Arity) of + {ok, Form} -> + NewForm = embed_params(Form, Values), + add_func(MetaMod, rename(NewForm, NewName)); + Err -> + Err + end. + + +%% @doc Apply the embed_params function with the list of {Name, Value} +%% pairs to all forms in the meta_mod. Exports +%% for functions whose arities change due to the embedding are preserved. +%% +%% @spec embed_all(MetaMod::meta_mod(), Vals::[{Name::atom(), +%% Value::term()}]) -> NewMetaMod::meta_mod() +embed_all(MetaMod, Vals) -> + Forms = get_forms(MetaMod), + Exports = get_exports(MetaMod), + {NewForms, Exports3, NewExports} = + lists:foldl( + fun({function, _L, Name, Arity, _Clauses} = Form, + {Forms1, Exports1, NewExports1}) -> + {function, _, _, NewArity, _} = NewForm = + embed_params(Form, Vals), + Exports2 = lists:delete({Name, Arity}, Exports1), + NewExports2 = + case length(Exports2) == length(Exports1) of + true -> + NewExports1; + false -> + [{Name, NewArity} | NewExports1] + end, + {[NewForm | Forms1], Exports2, NewExports2}; + (Form, {Forms1, Exports1, NewExports1}) -> + {[Form | Forms1], Exports1, NewExports1} + end, {[], Exports, []}, Forms), + #meta_mod{module = get_module(MetaMod), + exports = Exports3 ++ NewExports, + forms = lists:reverse(NewForms), + export_all = get_export_all(MetaMod)}. + +%% @doc extend/2 +%% Add all the parent module's functions that are missing from the child +%% module to the child module. The new functions in the child module are +%% shallow: they have the name and arity as their corresponding functions in +%% the parent meta_mod, but instead of implementing their logic they call +%% the parent module's functions. +%% +%% @spec extend(Parent::atom() | meta_mod(), Child::atom() | meta_mod()) -> +%% NewChildMod::meta_mod() +extend(Parent, Child) -> + extend(Parent, Child, 0). + +%% @doc Similar to extend/2, with the addition of the 'ArityDiff' parameter, +%% which indicates the difference +%% in arities meta should use when figuring out which functions to +%% generate based on the modules' exports. This is sometimes +%% useful when calling extend() followed by embed_all(). +%% +%% @spec extend(Parent::atom() | meta_mod(), Child::atom() | meta_mod(), +%% ArityDiff::integer()) -> +%% NewChildMod::meta_mod() +extend(Parent, Child, ArityDiff) -> + extend(Parent, Child, ArityDiff, []). + +extend(Parent, Child, ArityDiff, Options) -> + {{ParentName, ParentExports, ParentMod}, ChildMod} = + get_extend_data(Parent, Child), + ChildExports = get_exports(ChildMod), + ChildExports1 = [{ExportName, ExportArity + ArityDiff} || + {ExportName, ExportArity} <- + ChildExports], + ExportsDiff = ParentExports -- ChildExports1, + NewChild = + lists:foldl( + fun({FuncName, Arity}, ChildMod1) -> + Func = + case lists:member(copy, Options) of + true -> + {ok, ParentFunc} = + meta:get_func(ParentMod, FuncName, Arity), + ParentFunc; + _ -> + Params = get_params( + ParentMod, FuncName, Arity), + Clause1 = + {clause, 1, Params, [], + [{call, 1, + {remote, 1, {atom, 1, ParentName}, + {atom, 1, FuncName}}, + Params}]}, + {function, 1, FuncName, Arity, [Clause1]} + end, + {ok, ChildMod2} = add_func(ChildMod1, Func), + ChildMod2 + end, ChildMod, ExportsDiff), + NewChild. + +get_extend_data(Parent, Child) when is_atom(Parent) -> + [{exports, Exports} | _] = Parent:module_info(), + Exports1 = Exports -- [{module_info, 0}], + Exports2 = Exports1 -- [{module_info, 1}], + ParentMod = case meta:new_from_module(Parent) of + {ok, M} -> M; + {error, _} -> undefined + end, + get_extend_data({Parent, Exports2, ParentMod}, Child); +get_extend_data(Parent, Child) when is_record(Parent, meta_mod) -> + get_extend_data({get_module(Parent), + get_exports(Parent), + Parent}, Child); +get_extend_data(Parent, Child) when is_list(Parent) -> + case new_from_file(Parent) of + {ok, M1} -> + get_extend_data(M1, Child); + Err -> + Err + end; +get_extend_data({_, _, _} = ParentData, Child) when is_atom(Child); + is_list(Child) -> + case new_from_module(Child) of + {ok, MetaMod} -> + {ParentData, MetaMod}; + Err -> + Err + end; +get_extend_data(ParentData, Child) when is_record(Child, meta_mod) -> + {ParentData, Child}. + +get_params(_, _, 0) -> []; +get_params(undefined, _FuncName, Arity) -> + [{var, 1, list_to_atom("P" ++ integer_to_list(Num))} + || Num <- lists:seq(1, Arity)]; +get_params(ParentMod, FuncName, Arity) -> + {ok, {function, _L, _Name, _Arity, + [{clause, _, Params, _Guards, _Exprs} | _]}} = + get_func(ParentMod, FuncName, Arity), + Params. + + +%% @doc Return the pretty-printed source code for the module. +%% +%% @spec to_src(MetaMod::meta_mod()) -> string() +to_src(MetaMod) when is_record(MetaMod, meta_mod) -> + ExportsForm = + {attribute, 1, export, get_exports(MetaMod)}, + AllForms = [{attribute, 1, module, get_module(MetaMod)}, ExportsForm | + get_forms(MetaMod)], + erl_prettypr:format(erl_syntax:form_list(AllForms)). + +%% @doc Asb from to str. Asb return by get_func. +asb_to_src(Asb) -> + erl_prettypr:format(erl_syntax:form_list([Asb])). + +beam_to_src(Beam) -> + {ok, {_, [{abstract_code, {_, AC}}]}} = beam_lib:chunks(Beam, [abstract_code]), + io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]). + + +%% @doc Write the pretty printed source code for the module +%% to the file with the given file name. +%% +%% @spec to_src(MetaMod::meta_mod(), FileName::string()) -> +%% ok | {error, Error} +to_src(MetaMod, FileName) -> + Src = + io_lib:format("%% Warning: Do not edit this file. It was automatically\n" + "%% generated by 'meta' on ~p.\n\n", [calendar:local_time()]) + ++ + to_src(MetaMod), + file:write_file(FileName, list_to_binary(Src)). + + +%% =================================================================================== +%% form parse +%% =================================================================================== + +%% @doc get all records name. +-spec get_records_name(meta_mod()) -> [atom()]. +get_records_name(#meta_mod{records = Records}) -> + lists:map( + fun({attribute, _Line, record, {RecordName, _Fields}}) -> + RecordName end, + Records). + +-spec get_record(meta_mod(), atom()) -> {atom(), list()}. +get_record(#meta_mod{records = Records}, RecordName) -> + [{_, _, _, Record}] = + lists:filter( + fun({_Att, _L, record, {Name, _F}}) -> + if Name =:= RecordName -> true; + true -> false + end + end, + Records), + Record. diff --git a/src/dynamicCompile/meta_session.erl b/src/dynamicCompile/meta_session.erl new file mode 100644 index 0000000..f1d735e --- /dev/null +++ b/src/dynamicCompile/meta_session.erl @@ -0,0 +1,29 @@ +-module(meta_session). +-include("meta.hrl"). + +%% parse transform 'meta_session' + +-export([parse_transform/2, funstring_get/1]). + +parse_transform(Forms, _Options) -> + case meta:new_from_forms(Forms) of + {ok, Meta} -> + Module = meta:get_modulename(Meta), + {ok, MetaGet} = meta:add_func(Meta, funstring_get(Module)), + {ok, MetaSet} = meta:add_func(MetaGet, funstring_set(Module)), + {ok, MetaDel} = meta:add_func(MetaSet, funstring_del(Module)), + MetaFinal = MetaDel, + meta:to_forms(MetaFinal); + Error -> + io:format("parse_transform error-----------> ~p ~n", [Error]), + Forms + end. + +funstring_get(Module) -> + "get(Key, Default) -> com_util:dic_get({" ++ Module ++ ",Key},Default).". + +funstring_set(Module) -> + "set(Key, Value) -> com_util:dic_set({" ++ Module ++ ",Key},Value).". + +funstring_del(Module) -> + "del(Key) -> com_util:dic_erase({" ++ Module ++ ",Key}).". \ No newline at end of file diff --git a/src/dynamicCompile/reloader.erl b/src/dynamicCompile/reloader.erl new file mode 100644 index 0000000..5da9f7e --- /dev/null +++ b/src/dynamicCompile/reloader.erl @@ -0,0 +1,186 @@ + %% The MIT License (MIT) +%% +%% Copyright (c) 2014-2024 +%% Savin Max +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in all +%% copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%% SOFTWARE. +%% +%% @doc Erlang module for automatically reloading modified modules +%% during development. + +-module(reloader). + +-include_lib("kernel/include/file.hrl"). + +-behaviour(gen_server). +-export([start/0, start_link/0]). +-export([register_after_reload/1]). +-export([stop/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-export([all_changed/0]). +-export([is_changed/1]). +-export([reload_modules/1]). + +-record(state, {last, + tref, + after_reload_callback}). + +%% External API + +%% @spec start() -> ServerRet +%% @doc Start the reloader. +start() -> + gen_server:start({local, ?MODULE}, ?MODULE, [], []). + +%% @spec start_link() -> ServerRet +%% @doc Start the reloader. +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +%% @spec stop() -> ok +%% @doc Stop the reloader. +stop() -> + gen_server:call(?MODULE, stop). + +register_after_reload(Fun) -> + gen_server:call(?MODULE, {register_after_reload, Fun}). + +%% gen_server callbacks + +%% @spec init([]) -> {ok, State} +%% @doc gen_server init, opens the server in an initial state. +init([]) -> + {ok, TRef} = timer:send_interval(timer:seconds(1), doit), + {ok, #state{last = stamp(), tref = TRef}}. + +%% @spec handle_call(Args, From, State) -> tuple() +%% @doc gen_server callback. +handle_call(stop, _From, State) -> + {stop, shutdown, stopped, State}; +handle_call({register_after_reload, Fun}, _From, State) -> + {reply, ok, State#state{after_reload_callback = Fun}}; +handle_call(_Req, _From, State) -> + {reply, {error, badrequest}, State}. + +%% @spec handle_cast(Cast, State) -> tuple() +%% @doc gen_server callback. +handle_cast(_Req, State) -> + {noreply, State}. + +%% @spec handle_info(Info, State) -> tuple() +%% @doc gen_server callback. +handle_info(doit, #state{after_reload_callback = Fun} = State) -> + Now = stamp(), + ReloadedModules = doit(State#state.last, Now), + if + ReloadedModules =/= [] andalso Fun =/= undefined -> + Fun(ReloadedModules); + true -> ok + end, + {noreply, State#state{last = Now}}; +handle_info(_Info, State) -> + {noreply, State}. + +%% @spec terminate(Reason, State) -> ok +%% @doc gen_server termination callback. +terminate(_Reason, State) -> + {ok, cancel} = timer:cancel(State#state.tref), + ok. + + +%% @spec code_change(_OldVsn, State, _Extra) -> State +%% @doc gen_server code_change callback (trivial). +code_change(_Vsn, State, _Extra) -> + {ok, State}. + +%% @spec reload_modules([atom()]) -> [{module, atom()} | {error, term()}] +%% @doc code:purge/1 and code:load_file/1 the given list of modules in order, +%% return the results of code:load_file/1. +reload_modules(Modules) -> + [begin code:purge(M), code:load_file(M) end || M <- Modules]. + +%% @spec all_changed() -> [atom()] +%% @doc Return a list of beam modules that have changed. +all_changed() -> + [M || {M, Fn} <- code:all_loaded(), is_list(Fn), is_changed(M)]. + +%% @spec is_changed(atom()) -> boolean() +%% @doc true if the loaded module is a beam with a vsn attribute +%% and does not match the on-disk beam file, returns false otherwise. +is_changed(M) -> + try + module_vsn(M:module_info()) =/= module_vsn(code:get_object_code(M)) + catch _:_ -> + false + end. + +%% Internal API + +module_vsn({M, Beam, _Fn}) -> + {ok, {M, Vsn}} = beam_lib:version(Beam), + Vsn; +module_vsn(L) when is_list(L) -> + {_, Attrs} = lists:keyfind(attributes, 1, L), + {_, Vsn} = lists:keyfind(vsn, 1, Attrs), + Vsn. + +doit(From, To) -> + lists:foldl(fun({Module, Filename}, Acc) -> + case is_list(Filename) of + false -> Acc; + true -> + case file:read_file_info(Filename) of + {ok, #file_info{mtime = Mtime}} when Mtime >= From, Mtime < To -> + case reload(Module) of + error -> Acc; + _ -> [Module|Acc] + end; + {ok, _} -> + % unmodified; + Acc; + {error, enoent} -> + %% The Erlang compiler deletes existing .beam files if + %% recompiling fails. Maybe it's worth spitting out a + %% warning here, but I'd want to limit it to just once. + % gone; + Acc; + {error, Reason} -> + io:format("Error reading ~s's file info: ~p~n", + [Filename, Reason]), + % error + Acc + end + end + end, [], code:all_loaded()). + +reload(Module) -> + io:format("Reloading ~p ...", [Module]), + code:purge(Module), + case code:load_file(Module) of + {module, Module} -> + io:format(" ok.~n"), + reload; + {error, Reason} -> + io:format(" fail: ~p.~n", [Reason]), + error + end. + + +stamp() -> + erlang:localtime(). diff --git a/src/dynamicCompile/smerl.erl b/src/dynamicCompile/smerl.erl new file mode 100644 index 0000000..fd4e8d8 --- /dev/null +++ b/src/dynamicCompile/smerl.erl @@ -0,0 +1,1049 @@ +%% @author Yariv Sadan [http://yarivsblog.com] +%% @copyright Yariv Sadan 2006-2007 +%% +%% @doc Smerl: Simple Metaprogramming for Erlang +%% +%% Smerl is an Erlang library +%% that simplifies the creation and manipulation of Erlang modules in +%% runtime. +%% +%% You don't need to know Smerl in order to use ErlyWeb; Smerl +%% is included in ErlyWeb because ErlyWeb uses it internally. +%% +%% Smerl uses Erlang's capabilities for hot code swapping and +%% abstract syntax tree transformations to do its magic. Smerl is inspired by +%% the rdbms_codegen.erl module in the RDBMS application written by +%% Ulf Wiger. RDBMS is part of Jungerl ([http://jungerl.sf.net]). +%% +%% Here's a quick example illustrating how to use Smerl: +%% ``` +%% test_smerl() -> +%% M1 = smerl:new(foo), +%% {ok, M2} = smerl:add_func(M1, "bar() -> 1 + 1."), +%% smerl:compile(M2), +%% foo:bar(), % returns 2`` +%% smerl:has_func(M2, bar, 0). % returns true +%% ''' +%% +%% New functions can be expressed either as strings of Erlang code +%% or as abstract forms. For more information, read the Abstract Format +%% section in the ERTS User's guide +%% ([http://erlang.org/doc/doc-5.5/erts-5.5/doc/html/absform.html#4]). +%% +%% Using the abstract format, the 3rd line of the above example +%% would be written as +%% ``` +%% {ok,M2} = smerl:add_func(M1, {function,1,bar,0, +%% [{clause,1,[],[], +%% [{op,1,'+',{integer,1,1},{integer,1,1}}]}]). +%% ''' +%% +%%

The abstact format may look more verbose in this example, but +%% it's also easier to manipulate in code.

+%% + +%% Copyright (c) Yariv Sadan 2006-2007 +%% +%% Permission is hereby granted, free of charge, to any person +%% obtaining a copy of this software and associated documentation +%% files (the "Software"), to deal in the Software without restriction, +%% including without limitation the rights to use, copy, modify, merge, +%% publish, distribute, sublicense, and/or sell copies of the Software, +%% and to permit persons to whom the Software is furnished to do +%% so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included +%% in all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +%% IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +%% CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +%% TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +%% SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +-module(smerl). +-author("Yariv Sadan (yarivsblog@gmail.com, http://yarivsblog.com"). +-export([new/1, + for_module/1, + for_module/2, + for_module/3, + for_file/1, + for_file/2, + for_file/3, + get_module/1, + set_module/2, + get_forms/1, + set_forms/2, + get_exports/1, + set_exports/2, + get_export_all/1, + set_export_all/2, + remove_export/3, + get_attribute/2, + add_func/2, + add_func/3, + remove_func/3, + has_func/3, + get_func/3, + replace_func/2, +% replace_func/3, + compile/1, + compile/2, + rename/2, + curry/2, + curry/4, + curry/5, + curry_add/3, + curry_add/4, + curry_add/5, + curry_add/6, + curry_replace/3, + curry_replace/4, + embed_params/2, + embed_params/4, + embed_params/5, + embed_all/2, + extend/2, + extend/3, + extend/4, + to_src/1, + to_src/2 + ]). + +-define(L(Obj), io:format("LOG ~s ~w ~p\n", [?FILE, ?LINE, Obj])). +-define(S(Obj), io:format("LOG ~s ~w ~s\n", [?FILE, ?LINE, Obj])). + +-include_lib("kernel/include/file.hrl"). + +%% @type meta_mod(). A data structure holding the abstract representation +%% for a module. +%% @type func_form(). The abstract form for the function, as described +%% in the ERTS Users' manual. + +%% The record type holding the abstract representation for a module. +-record(meta_mod, {module, file, exports = [], forms = [], + export_all = false}). + +%% @doc Create a new meta_mod for a module with the given name. +%% +%% @spec new(Module::atom()) -> meta_mod() +new(ModuleName) when is_atom(ModuleName) -> + #meta_mod{module = ModuleName}. + + +%% @equiv for_module(ModuleName, []) +for_module(ModuleName) -> + for_module(ModuleName, []). + +%% @equiv for_module(ModuleName, IncludePaths, []) +for_module(ModuleName, IncludePaths) -> + for_module(ModuleName, IncludePaths, []). + +%% @doc Create a meta_mod tuple for an existing module. If ModuleName is a +%% string, it is interpreted as a file name (this is the same as calling +%% @{link smerl:for_file}). If ModuleName is an atom, Smerl attempts to +%% find its abstract represtation either from its source file or from +%% its .beam file directly (if it has been compiled with debug_info). +%% If the abstract representation can't be found, this function returns +%% an error. +%% +%% The IncludePaths parameter is used when 'ModuleName' is a file name. +%% +%% @spec for_module(ModuleName::atom() | string(), IncludePaths::[string()], +%% Macros::[{atom(), term()}]) -> +%% {ok, meta_mod()} | {error, Error} +for_module(ModuleName, IncludePaths, Macros) when is_list(ModuleName) -> + for_file(ModuleName, IncludePaths, Macros); +for_module(ModuleName, IncludePaths, Macros) when is_atom(ModuleName) -> + [_Module, _Exports, _Attributes, + {compile, [_Options, _Version, _Time, {source, Path}]}, + {native, _}, {md5, _}] = + ModuleName:module_info(), + case for_file(Path, IncludePaths, Macros) of + {ok, _Mod} = Res-> + Res; + _Err -> + case code:which(ModuleName) of + Path1 when is_list(Path1) -> + case get_forms(ModuleName, Path1) of + {ok, Forms} -> + mod_for_forms(Forms); + _Other -> + {error, {invalid_module, ModuleName}} + end; + _Err -> + {error, {invalid_module, ModuleName}} + end + end. + +%% @equiv for_file(SrcFilePath, []) +for_file(SrcFilePath) -> + for_file(SrcFilePath, []). + +%% @equiv for_file(SrcFilePath, IncludePaths, []) +for_file(SrcFilePath, IncludePaths) -> + for_file(SrcFilePath, IncludePaths, []). + +%% @doc Create a meta_mod for a module from its source file. +%% +%% @spec for_file(SrcFilePath::string(), IncludePaths::[string()], +%% Macros::[{atom(), term()}]) -> +%% {ok, meta_mod()} | {error, invalid_module} +for_file(SrcFilePath, IncludePaths, Macros) -> + case epp:parse_file(SrcFilePath, [filename:dirname(SrcFilePath) | + IncludePaths], Macros) of + {ok, Forms} -> + mod_for_forms(Forms); + _err -> + {error, {invalid_module, SrcFilePath}} + end. + +mod_for_forms([{attribute,_,file,{FileName,_FileNum}}, + {attribute, _, module, ModuleName}|Forms]) -> + {Exports, OtherForms, ExportAll} = + lists:foldl( + fun({attribute, _, export, ExportList}, + {ExportsAcc, FormsAcc, ExportAll}) -> + {ExportList ++ ExportsAcc, FormsAcc, ExportAll}; + ({attribute, _, compile, export_all}, + {ExportsAcc, FormsAcc, _ExportAll}) -> + {ExportsAcc, FormsAcc, true}; + ({eof, _}, Acc) -> + Acc; + (Form, {ExportsAcc, FormsAcc, ExportAll}) -> + {ExportsAcc, [Form | FormsAcc], ExportAll} + end, {[], [], false}, Forms), + {ok, #meta_mod{module = ModuleName, + file = FileName, + exports = Exports, + forms = OtherForms, + export_all = ExportAll + }}; +mod_for_forms(Mod) -> + {error, {invalid_module, Mod}}. + +%% @doc Return the module name for the meta_mod. +%% +%% @spec(MetaMod::meta_mod()) -> atom() +get_module(MetaMod) -> + MetaMod#meta_mod.module. + +%% @doc Set the meta_mod's module name. +%% +%% @spec set_module(MetaMod::meta_mod(), NewName::atom()) -> +%% NewMod::meta_mod() +set_module(MetaMod, NewName) -> + MetaMod#meta_mod{module = NewName}. + +%% @doc Return the list of function forms in the meta_mod. +%% +%% @spec get_forms(MetaMod::meta_mod()) -> [Form] +get_forms(MetaMod) -> + MetaMod#meta_mod.forms. + +set_forms(MetaMod, Forms) -> + MetaMod#meta_mod{forms = Forms}. + +%% @doc Return the list of exports in the meta_mod. +%% +%% @spec get_exports(MetaMod::meta_mod()) -> +%% [{FuncName::atom(), Arity::integer()}] +get_exports(MetaMod) -> + case MetaMod#meta_mod.export_all of + false -> + MetaMod#meta_mod.exports; + true -> + lists:foldl( + fun({function, _L, Name, Arity, _Clauses}, Exports) -> + [{Name, Arity} | Exports]; + (_Form, Exports) -> + Exports + end, [], MetaMod#meta_mod.forms) + end. + +%% @doc Set the meta_mod's export list to the new list. +%% +%% @spec set_exports(MetaMod::meta_mod(), +%% Exports::[{FuncName::atom(), Arity::integer()}]) -> NewMod::meta_mod() +set_exports(MetaMod, Exports) -> + MetaMod#meta_mod{exports = Exports}. + +%% @doc Get the export_all value for the module. +%% +%% @spec get_export_all(MetaMod::meta_mod) -> true | false +get_export_all(MetaMod) -> + MetaMod#meta_mod.export_all. + +%% @doc Set the export_all value for the module. +%% +%% @spec set_export_all(MetaMod::meta_mod(), Val::true | false) -> +%% NewMetaMod::meta_mod() +set_export_all(MetaMod, Val) -> + MetaMod#meta_mod{export_all = Val}. + +%% @doc Remove the export from the list of exports in the meta_mod. +%% +%% @spec remove_export(MetaMod::meta_mod(), FuncName::atom(), +%% Arity::integer()) -> NewMod::meta_mod() +remove_export(MetaMod, FuncName, Arity) -> + MetaMod#meta_mod{exports = + lists:delete({FuncName, Arity}, + MetaMod#meta_mod.exports)}. + +%% @doc Get the value a the module's attribute. +%% +%% @spec get_attribute(MetaMod::meta_mod(), AttName::atom()) -> +%% {ok, Val} | error +get_attribute(MetaMod, AttName) -> + case lists:keysearch(AttName, 3, get_forms(MetaMod)) of + {value, {attribute,_,_,Val}} -> + {ok, Val}; + _ -> error + end. + + +%% Get the abstract representation, if available, for the module. +%% +%% Strategy: +%% 1) Try to get the abstract code from the module if it's compiled +%% with debug_info. +%% 2) Look for the source file in the beam file's directory. +%% 3) If the file's directory ends with 'ebin', then search in +%% [beamdir]/../src +get_forms(Module, Path) -> + case beam_lib:chunks(Path, [abstract_code]) of + {ok, {_, [{abstract_code, {raw_abstract_v1, Forms}}]}} -> + {ok, Forms}; + _Err -> + case filename:find_src(Module, [{"ebin", "src"}]) of + {error, _} = Err -> + get_forms_from_binary(Module, Err); + {SrcPath, _} -> + Filename = SrcPath ++ ".erl", + epp:parse_file(Filename, [filename:dirname(Filename)], []) + end + end. + +get_dirs_in_dir(Dir) -> + case file:list_dir(Dir) of + {error, _} -> + undefined; + {ok, Listing} -> + lists:foldl( + fun (Name, Acc) -> + Path = Dir ++ "/" ++ Name, + case file:read_file_info(Path) of + {ok, #file_info{type=directory}} -> [Path | Acc]; + _ -> Acc + end + end, [], Listing) + end. + +%% @doc Try to infer module source files from the beam code path. +get_forms_from_binary(Module, OrigErr) -> + Ret = + case code:where_is_file(atom_to_list(Module) ++ ".beam") of + non_existing -> + OrigErr; + Filename -> + %% We could automatically obtain a list of all dirs under this dir, but we just do + %% a hack for now. + Basedir = filename:dirname(Filename), + Lastdir = filename:basename(Basedir), + if Lastdir == "ebin" -> + Rootdir = filename:dirname(Basedir), + DirList = [Rootdir ++ "/src"], + get_forms_from_file_list(Module, Rootdir, + DirList ++ get_dirs_in_dir(Rootdir ++ "/src")); + true -> + DirList = [Basedir], + get_forms_from_file_list(Module, Basedir, DirList) + end + end, + if Ret == [] -> OrigErr; + true -> Ret + end. +get_forms_from_file_list(_Module, _Basedir, []) -> + []; +get_forms_from_file_list(Module, Basedir, [H|T]) -> + Filename = H ++ "/" ++ atom_to_list(Module) ++ ".erl", + case file:read_file_info(Filename) of + {ok, #file_info{type=regular}} -> + epp:parse_file(Filename, [filename:dirname(Filename)], []); + _ -> + get_forms_from_file_list(Module, Basedir, T) + end. + +%% @doc Add a new function to the meta_mod and return the resulting meta_mod. +%% This function calls add_func(MetaMod, Form, true). +%% +%% @spec add_func(MetaMod::meta_mod(), Form::func_form() | string()) -> +%% {ok, NewMod::meta_mod()} | {error, parse_error} +add_func(MetaMod, Form) -> + add_func(MetaMod, Form, true). + +%% @doc Add a new function to the meta_mod and return the new MetaMod +%% record. Export is a boolean variable indicating if the function should +%% be added to the module's exports. +%% +%% @spec add_func(MetaMod::meta_mod(), Func::func_form() | string(), +%% Export::boolean()) -> +%% {ok, NewMod::meta_mod()} | {error, parse_error} +add_func(MetaMod, Func, Export) when is_list(Func) -> + case parse_func_string(Func) of + {ok, Form} -> + add_func(MetaMod, Form, Export); + Err -> + Err + end; +add_func(MetaMod, {function, _Line, FuncName, Arity, _Clauses} = Form, + true) -> + Foo = {ok, MetaMod#meta_mod{ + exports = [{FuncName, Arity} | MetaMod#meta_mod.exports], + forms = [Form | MetaMod#meta_mod.forms] + }}, + Foo; +add_func(MetaMod, {function, _Line, _FuncName, _Arity, _Clauses} = Form, + false) -> + {ok, MetaMod#meta_mod{forms = [Form | MetaMod#meta_mod.forms]}}; + +%%add_func(MetaMod, Name, Fun) when is_function(Fun) -> +%% add_func(MetaMod, Name, Fun, true); + +add_func(_, _, _) -> + {error, parse_error}. + +%% add_func(MetaMod, Name, Fun, Export) when is_function(Fun) -> +%% case form_for_fun(Name, Fun) of +%% {ok, Form} -> +%% add_func(MetaMod, Form, Export); +%% Err -> +%% Err +%% end. + +%% form_for_fun(Name, Fun) -> +%% Line = 999, +%% Info = erlang:fun_info(Fun), +%% case Info of +%% [{module, _ModName}, _FuncName, _Arity, _Env, {type, external}] -> +%% {error, cant_add_external_funcs}; +%% [_Pid, _Module, _NewIdx, _NewUniq, _Index, _Uniq, _Name, +%% {arity, Arity}, +%% {env, [Vars, _Unknown1, _Unknown2, Clauses]}, +%% {type, local}] -> +%% EnvVars = lists:map( +%% fun({VarName, Val}) -> +%% {match,Line,{var,Line,VarName}, +%% erl_parse:abstract(Val)} +%% end, Vars), +%% NewClauses = lists:map( +%% fun({clause, Line1, Params, Guards, Exprs}) -> +%% {clause, Line1, Params, Guards, +%% EnvVars ++ Exprs} +%% end, Clauses), +%% {ok, {function, Line, Name, Arity, NewClauses}}; +%% _Other -> +%% {error, bad_fun} +%% end. + + +parse_func_string(Func) -> + case erl_scan:string(Func) of + {ok, Toks, _} -> + case erl_parse:parse_form(Toks) of + {ok, _Form} = Res -> + Res; + _Err -> + {error, parse_error} + end; + _Err -> + {error, parse_error} + end. + +%% @doc Try to remove the function from the meta_mod. +%% If the function exists, the new meta_mod is returned. Otherwise, +%% original meta_mod is returned. +%% +%% @spec remove_func(MetaMod::meta_mod(), FuncName::string(), Arity::integer()) +%% -> NewMod::meta_mod() +%% +remove_func(MetaMod, FuncName, Arity) -> + MetaMod#meta_mod{forms = + lists:filter( + fun({function, _Line, FuncName1, Arity1, _Clauses}) + when FuncName1 =:= FuncName, Arity =:= Arity1-> + false; + (_) -> + true + end, MetaMod#meta_mod.forms), + exports = + lists:filter( + fun({FuncName1, Arity1}) + when FuncName1 =:= FuncName, + Arity1 =:= Arity -> + false; + (_) -> + true + end, MetaMod#meta_mod.exports) + }. + +%% @doc Check whether the meta_mod has a function with the given name +%% and arity. +%% @spec has_func(MetaMod::meta_mod(), FuncName::atom(), Arity::integer()) -> +%% bool() +has_func(MetaMod, FuncName, Arity) -> + lists:any(fun({function, _Line, FuncName1, Arity1, _Clauses}) + when FuncName1 == FuncName, Arity1 == Arity -> + true; + (_) -> + false + end, MetaMod#meta_mod.forms). + + +%% @doc Get the form for the function with the specified arity in the +%% meta_mod. +%% +%% @spec get_func(MetaMod::meta_mod() | atom(), +%% FuncName::atom(), Arity::integer()) -> +%% {ok, func_form()} | {error, Err} +get_func(Module, FuncName, Arity) when is_atom(Module) -> + case smerl:for_module(Module) of + {ok, C1} -> + get_func(C1, FuncName, Arity); + Err -> + Err + end; +get_func(MetaMod, FuncName, Arity) -> + get_func2(MetaMod#meta_mod.forms, FuncName, Arity). + +get_func2([], FuncName, Arity) -> + {error, {function_not_found, {FuncName, Arity}}}; +get_func2([{function, _Line, FuncName, Arity, _Clauses} = Form | _Rest], + FuncName, Arity) -> + {ok, Form}; +get_func2([_Form|Rest], FuncName, Arity) -> + get_func2(Rest, FuncName, Arity). + + + +%% @doc +%% Replace an existing function with the new one. If the function doesn't exist +%% the new function is added to the meta_mod. +%% This is tantamount to calling smerl:remove_func followed by smerl:add_func. +%% +%% @spec replace_func(MetaMod::meta_mod(), Function::string() | func_form()) -> +%% {ok, NewMod::meta_mod()} | {error, Error} +replace_func(MetaMod, Function) when is_list(Function) -> + case parse_func_string(Function) of + {ok, Form} -> + replace_func(MetaMod, Form); + Err -> + Err + end; +replace_func(MetaMod, {function, _Line, FuncName, Arity, _Clauses} = Form) -> + Mod1 = remove_func(MetaMod, FuncName, Arity), + add_func(Mod1, Form); +replace_func(_MetaMod, _) -> + {error, parse_error}. + +%% %% @doc Simliar to replace_func/2, but accepts a function +%% %% name + fun expression. +%% %% +%% %% @spec replace_func(MetaMod::meta_mod(), Name::atom(), Fun::function()) -> +%% %% {ok, NewMod::meta_mod()} | {error, Error} +%% replace_func(MetaMod, Name, Fun) when is_function(Fun) -> +%% case form_for_fun(Name, Fun) of +%% {ok, Form} -> +%% replace_func(MetaMod, Form); +%% Err -> +%% Err +%% end. + +%% @doc Compile the module represented by the meta_mod and load the +%% resulting BEAM into the emulator. This function calls +%% compile(MetaMod, [report_errors, report_warnings]). +%% +%% @spec compile(MetaMod::meta_mod()) -> ok | {error, Error} +compile(MetaMod) -> + compile(MetaMod, undefined). + +%% @doc Compile the module represented by the meta_mod and load the +%% resulting BEAM into the emulator. 'Options' is a list of options as +%% described in the 'compile' module in the Erlang documentation. +%% +%% If the 'outdir' option is provided, +%% the .beam file is written to the destination directory. +%% +%% @spec compile(MetaMod::meta_mod(), Options::[term()]) -> ok | {error, Error} +compile(MetaMod, undefined) -> + compile(MetaMod, [report_errors, report_warnings, + return_errors]); + +compile(MetaMod, Options) -> + Forms = [{attribute, 2, module, MetaMod#meta_mod.module}, + {attribute, 3, export, get_exports(MetaMod)}], + FileName = + case MetaMod#meta_mod.file of + undefined -> atom_to_list(get_module(MetaMod)); + Val -> Val + end, + + Forms1 = [{attribute, 1, file, {FileName, 1}} | Forms], + Forms2 = Forms1 ++ lists:reverse(MetaMod#meta_mod.forms), + + case compile:forms(Forms2, Options) of + {ok, Module, Bin} -> + Res = + case lists:keysearch(outdir, 1, Options) of + {value, {outdir, OutDir}} -> + file:write_file( + OutDir ++ + ['/' | atom_to_list(MetaMod#meta_mod.module)] ++ + ".beam", Bin); + false -> ok + end, + case Res of + ok -> + code:purge(Module), + case code:load_binary( + Module, + atom_to_list(Module) ++ ".erl", Bin) of + {module, _Module} -> + ok; + Err -> + Err + end; + Err -> + Err + end; + Err -> + Err + end. + +%% @doc Change the name of the function represented by the form. +%% +%% @spec rename(Form::func_form(), NewName::atom()) -> +%% {ok, NewForm::func_form()} | {error, Err} +rename({function, Line, _Name, Arity, Clauses}, NewName) -> + {function, Line, NewName, Arity, Clauses}. + + +%% @doc Get the curried form for the function and parameter(s). Currying +%% involves replacing one or more of the function's leading parameters +%% with predefined values. +%% +%% @spec curry(Form::func_form(), Param::term() | [term()]) -> +%% {ok, NewForm::func_form()} | {error, Err} +curry(Form, Param) when not is_list(Param) -> + curry(Form, [Param]); +curry({function, _Line, _Name, Arity, _Clauses}, Params) + when length(Params) > Arity -> + {error, too_many_params}; +curry({function, Line, Name, Arity, Clauses}, NewParams) -> + NewClauses = + lists:foldl( + fun(Clause, Clauses1) -> + [curry_clause(Clause, NewParams) | Clauses1] + end, [], Clauses), + {ok, {function, Line, Name, Arity-length(NewParams), NewClauses}}. + +curry_clause({clause, L1, ExistingParams, Guards, _Exprs} = Clause, + NewParams) -> + {FirstParams, LastParams} = + lists:split(length(NewParams), ExistingParams), +%% Matches = +%% lists:foldl( +%% fun({Var, NewVal}, Acc) -> +%% [{match, 1, Var, erl_parse:abstract(NewVal)} | Acc] +%% end, [], lists:zip(FirstParams, NewParams)), +%% {clause, L1, LastParams, Guards, Matches ++ Exprs}. + + Vals = + lists:foldl( + fun({{var,_,Name}, NewVal}, Acc) -> + [{Name, erl_parse:abstract(NewVal)} | Acc]; + (_, Acc) -> + Acc + end, [], lists:zip(FirstParams, NewParams)), + + NewExprs = replace_vars(Clause, Vals), + + {clause, L1, LastParams, Guards, NewExprs}. + +replace_vars(Clause, Vals) -> + Tree = + erl_syntax_lib:map( + fun({var,_L2,Name} = Expr) -> + case proplists:lookup(Name, Vals) of + none -> + Expr; + {_, Val} -> + Val + end; + (Expr) -> + Expr + end, Clause), + {clause, _, _, _, NewExprs} = erl_syntax:revert(Tree), + NewExprs. + + +%% @doc Curry the function from the module with the given param(s) +%% +%% @spec curry(ModName::atom(), Name::atom(), Arity::integer(), +%% Params::term() | [term()]) -> +%% {ok, NewForm} | {error, Err} +curry(ModName, Name, Arity, Params) when is_atom(ModName) -> + case for_module(ModName) of + {ok, MetaMod} -> + curry(MetaMod, Name, Arity, Params); + Err -> + Err + end; + +%% @doc Curry the function from the meta_mod with +%% the given param(s) +%% +%% @spec curry(MetaMod::meta_mod(), Name::atom(), arity::integer(), +%% Params::term() | [terms()]) -> +%% {ok, NewForm} | {error, Err} +curry(MetaMod, Name, Arity, Params) -> + case get_func(MetaMod, Name, Arity) of + {ok, Form} -> + curry(Form, Params); + Err -> + Err + end. + + + +%% @doc Curry the function from the module or meta_mod +%% with the param(s), and return its renamed form. +%% +%% @spec curry(Module::atom() | meta_mod(), Name::atom(), Arity::integer(), +%% Params::term() | [terms()], NewName::atom()) -> +%% {ok, NewForm} | {error, Err} +curry(Module, Name, Arity, Params, NewName) -> + case curry(Module, Name, Arity, Params) of + {ok, NewForm} -> + {ok, rename(NewForm, NewName)}; + Err -> + Err + end. + + +%% @doc Add the curried form of the function in the meta_mod +%% with its curried form. +%% +%% @spec curry_add(MetaMod::meta_mod(), Form::func_form(), +%% Params::term() | [term()]) -> +%% {ok, NewMetaMod::meta_mod()} | {error, Err} +curry_add(MetaMod, {function, _Line, Name, Arity, _Clauses}, Params) -> + curry_add(MetaMod, Name, Arity, Params). + +%% @doc Add the curried form of the function +%% in the meta_mod with its curried form. +%% +%% @spec curry_add(MetaMod::meta_mod(), Name::atom(), Arity::integer(), +%% Params::term() | [term()]) -> +%% {ok, NewMetaMod::meta_mod()} | {error, Err} +curry_add(MetaMod, Name, Arity, Params) -> + curry_change(MetaMod, Name, Arity, Params, false). + +%% @doc Curry the function form from the meta_mod, then add it +%% to the other meta_mod with a new name. +%% +%% @spec curry_add(MetaMod::meta_mod(), Name::atom(), Arity::integer(), +%% Params::[term()], NewName::atom()) -> {ok, NewMod::meta_mod()} | +%% {error, Err} +curry_add(MetaMod, Name, Arity, Params, NewName) -> + curry_add(MetaMod, MetaMod, Name, Arity, Params, NewName). + +%% @doc Curry the function in the module, rename the curried form, and +%% add it to the meta_mod. +%% +%% @spec curry_add(MetaMod::meta_mod(), Module::atom() | meta_mod(), +%% Name::atom(), Arity::integer(), Params::term() | [term()], +%% NewName::atom()) -> +%% {ok, NewMod::meta_mod()} | {error, Error} +curry_add(MetaMod, Module, Name, Arity, Params, NewName) -> + case curry(Module, Name, Arity, Params, NewName) of + {ok, Form} -> + add_func(MetaMod, Form); + Err -> + Err + end. + +curry_change(MetaMod, Name, Arity, Params, Remove) -> + case get_func(MetaMod, Name, Arity) of + {ok, OldForm} -> + case curry(OldForm, Params) of + {ok, NewForm} -> + MetaMod1 = + case Remove of + true -> + remove_func(MetaMod, Name, Arity); + false -> + MetaMod + end, + add_func(MetaMod1, NewForm); + Err -> + Err + end; + Err -> + Err + end. + +%% @doc Replace the function in the meta_mod with +%% its curried form. +%% +%% @spec curry_replace(MetaMod::meta_mod(), Form::func_form(), +%% Params::term() | [term()]) -> +%% {ok, NewMetaMod::meta_mod()} | {error, Err} +curry_replace(MetaMod, {function, _Line, Name, Arity, _Clauses}, Params) -> + curry_replace(MetaMod, Name, Arity, Params). + + +%% @doc Replace the function in the meta_mod with +%% its curried form. +%% +%% @spec curry_replace(MetaMod::meta_mod(), Name::atom(), +%% Arity::integer(), Params::term() | list()) -> +%% {ok, NewMetaMod::meta_mod()} | {error, Err} +curry_replace(MetaMod, Name, Arity, Params) -> + curry_change(MetaMod, Name, Arity, Params, true). + + +%% @doc This function takes a function form and list of name/value pairs, +%% and replaces all the function's parameters that whose names match an +%% element from the list with the predefined value. +%% +%% @spec embed_params(Func::func_form(), +%% Vals::[{Name::atom(), Value::term()}]) -> NewForm::func_form() +embed_params({function, L, Name, Arity, Clauses}, Vals) -> + NewClauses = + lists:map( + fun({clause, L1, Params, Guards, _Exprs} = Clause) -> + {EmbeddedVals, OtherParams} = + lists:foldr( + fun({var,_, VarName} = Param, {Embedded, Rest}) -> + case proplists:lookup(VarName, Vals) of + none -> + {Embedded, [Param | Rest]}; + {_, Val} -> + {[{VarName, erl_parse:abstract(Val)} | + Embedded], Rest} + end; + (Param, {Embedded, Rest}) -> + {Embedded, [Param | Rest]} + end, {[], []}, Params), + NewExprs = replace_vars(Clause, EmbeddedVals), + {clause, L1, OtherParams, Guards, NewExprs} + + +%% {Params1, Matches1, _RemainingVals} = +%% lists:foldl( +%% fun({var, _L2, ParamName} = Param, +%% {Params2, Matches2, Vals1}) -> +%% case lists:keysearch(ParamName, 1, Vals1) of +%% {value, {_Name, Val} = Elem} -> +%% Match = {match, L1, Param, +%% erl_parse:abstract(Val)}, +%% {Params2, [Match | Matches2], +%% lists:delete(Elem, Vals1)}; +%% false -> +%% {[Param | Params2], Matches2, Vals1} +%% end; +%% (Param, {Params2, Matches2, Vals1}) -> +%% {[Param | Params2], Matches2, Vals1} +%% end, {[], [], Vals}, Params), +%% [{clause, L1, lists:reverse(Params1), Guards, +%% lists:reverse(Matches1) ++ Exprs} | Clauses1] + end, Clauses), + NewArity = + case NewClauses of + [{clause, _L2, Params, _Guards, _Exprs}|_] -> + length(Params); + _ -> + Arity + end, + {function, L, Name, NewArity, NewClauses}. + +%% @doc Apply {@link embed_params/2} to a function from the meta_mod and +%% add the resulting function to the meta_mod, and return the resulting +%% meta_mod. +%% +%% @spec embed_params(MetaMod::meta_mod(), Name::atom(), Arity::integer(), +%% Values::proplist()) -> {ok, NewMetaMod::meta_mod()} | {error, Err} +embed_params(MetaMod, Name, Arity, Values) -> + embed_params(MetaMod, Name, Arity, Values, Name). + +%% @doc Apply embed_params/2 to the function from the meta_mod and +%% add the resulting function to the meta_mod after renaming the function. +%% +%% @spec embed_params(MetaMod::meta_mod(), Name::atom(), Arity::integer(), +%% Values::proplist(), NewName::atom()) -> +%% {ok, NewMetaMod::meta_mod()} | {error, Err} +embed_params(MetaMod, Name, Arity, Values, NewName) -> + case get_func(MetaMod, Name, Arity) of + {ok, Form} -> + NewForm = embed_params(Form, Values), + add_func(MetaMod, rename(NewForm, NewName)); + Err -> + Err + end. + + + + +%% @doc Apply the embed_params function with the list of {Name, Value} +%% pairs to all forms in the meta_mod. Exports +%% for functions whose arities change due to the embedding are preserved. +%% +%% @spec embed_all(MetaMod::meta_mod(), Vals::[{Name::atom(), +%% Value::term()}]) -> NewMetaMod::meta_mod() +embed_all(MetaMod, Vals) -> + Forms = get_forms(MetaMod), + Exports = get_exports(MetaMod), + {NewForms, Exports3, NewExports} = + lists:foldl( + fun({function, _L, Name, Arity, _Clauses} = Form, + {Forms1, Exports1, NewExports1}) -> + {function, _, _, NewArity, _} = NewForm = + embed_params(Form, Vals), + Exports2 = lists:delete({Name, Arity}, Exports1), + NewExports2 = + case length(Exports2) == length(Exports1) of + true -> + NewExports1; + false -> + [{Name, NewArity} | NewExports1] + end, + {[NewForm | Forms1], Exports2, NewExports2}; + (Form, {Forms1, Exports1, NewExports1}) -> + {[Form | Forms1], Exports1, NewExports1} + end, {[], Exports, []}, Forms), + #meta_mod{module = get_module(MetaMod), + exports = Exports3 ++ NewExports, + forms = lists:reverse(NewForms), + export_all = get_export_all(MetaMod)}. + +%% @doc extend/2 +%% Add all the parent module's functions that are missing from the child +%% module to the child module. The new functions in the child module are +%% shallow: they have the name and arity as their corresponding functions in +%% the parent meta_mod, but instead of implementing their logic they call +%% the parent module's functions. +%% +%% @spec extend(Parent::atom() | meta_mod(), Child::atom() | meta_mod()) -> +%% NewChildMod::meta_mod() +extend(Parent, Child) -> + extend(Parent, Child, 0). + +%% @doc Similar to extend/2, with the addition of the 'ArityDiff' parameter, +%% which indicates the difference +%% in arities Smerl should use when figuring out which functions to +%% generate based on the modules' exports. This is sometimes +%% useful when calling extend() followed by embed_all(). +%% +%% @spec extend(Parent::atom() | meta_mod(), Child::atom() | meta_mod(), +%% ArityDiff::integer()) -> +%% NewChildMod::meta_mod() +extend(Parent, Child, ArityDiff) -> + extend(Parent, Child, ArityDiff, []). + +extend(Parent, Child, ArityDiff, Options) -> + {{ParentName, ParentExports, ParentMod}, ChildMod} = + get_extend_data(Parent, Child), + ChildExports = get_exports(ChildMod), + ChildExports1 = [{ExportName, ExportArity + ArityDiff} || + {ExportName, ExportArity} <- + ChildExports], + ExportsDiff = ParentExports -- ChildExports1, + NewChild = + lists:foldl( + fun({FuncName, Arity}, ChildMod1) -> + Func = + case lists:member(copy, Options) of + true -> + {ok, ParentFunc} = + smerl:get_func(ParentMod, FuncName, Arity), + ParentFunc; + _ -> + Params = get_params( + ParentMod, FuncName, Arity), + Clause1 = + {clause,1,Params,[], + [{call,1, + {remote,1,{atom,1,ParentName}, + {atom,1,FuncName}}, + Params}]}, + {function,1,FuncName,Arity, [Clause1]} + end, + {ok, ChildMod2} = add_func(ChildMod1, Func), + ChildMod2 + end, ChildMod, ExportsDiff), + NewChild. + +get_extend_data(Parent, Child) when is_atom(Parent) -> + [{exports, Exports} |_] = Parent:module_info(), + Exports1 = Exports -- [{module_info, 0}], + Exports2 = Exports1 -- [{module_info, 1}], + ParentMod = case smerl:for_module(Parent) of + {ok, M} -> M; + {error, _} -> undefined + end, + get_extend_data({Parent, Exports2, ParentMod}, Child); +get_extend_data(Parent, Child) when is_record(Parent, meta_mod) -> + get_extend_data({get_module(Parent), + get_exports(Parent), + Parent}, Child); +get_extend_data(Parent, Child) when is_list(Parent) -> + case for_file(Parent) of + {ok, M1} -> + get_extend_data(M1, Child); + Err -> + Err + end; +get_extend_data({_,_,_} = ParentData, Child) when is_atom(Child); + is_list(Child) -> + case for_module(Child) of + {ok, MetaMod} -> + {ParentData, MetaMod}; + Err -> + Err + end; +get_extend_data(ParentData, Child) when is_record(Child, meta_mod) -> + {ParentData, Child}. + +get_params(_, _, 0) -> []; +get_params(undefined, _FuncName, Arity) -> + [{var,1,list_to_atom("P" ++ integer_to_list(Num))} + || Num <- lists:seq(1, Arity)]; +get_params(ParentMod, FuncName, Arity) -> + {ok, {function, _L, _Name, _Arity, + [{clause,_,Params,_Guards,_Exprs} | _]}} = + get_func(ParentMod, FuncName, Arity), + Params. + + +%% @doc Return the pretty-printed source code for the module. +%% +%% @spec to_src(MetaMod::meta_mod()) -> string() +to_src(MetaMod) -> + ExportsForm = + {attribute,1,export,get_exports(MetaMod)}, + AllForms = [{attribute,1,module,get_module(MetaMod)}, ExportsForm | + get_forms(MetaMod)], + erl_prettypr:format(erl_syntax:form_list(AllForms)). + +%% @doc Write the pretty printed source code for the module +%% to the file with the given file name. +%% +%% @spec to_src(MetaMod::meta_mod(), FileName::string()) -> +%% ok | {error, Error} +to_src(MetaMod, FileName) -> + Src = to_src(MetaMod), + file:write_file(FileName, list_to_binary(Src)). diff --git a/src/httpSocket/http.erl b/src/httpSocket/http.erl new file mode 100644 index 0000000..828ee78 --- /dev/null +++ b/src/httpSocket/http.erl @@ -0,0 +1,159 @@ +-module(http). + +-behaviour(gen_server). + +-define(JSON_CONTENT, {"Content-Type", "application/json"}). +-define(HTTP_CLIENT_TIMEOUT, 10000). +%% Connection pool size: 100, Each connection queue size: 100 +-define(HTTP_CLIENT_OPTIONS, [{max_sessions, 100}, {max_pipeline_size, 100}]). + +-define(SERVER, ?MODULE). +-define(RESEND_AFTER_DELAY, 20000). % 20 secs +-define(MAX_RETRY, 13). + +%% API +-export([start_link/0, + request/3, + async_request/3, + queue_request/3, + queue_request/4]). + +%% gen_server callbacks +-export([init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3]). + +-record(state, {}). + +request(Url, Method, Params) -> + request(Url, Method, Params, false). + +async_request(Url, Method, Params) -> + request(Url, Method, Params, true). + +request(Url, Method, Params, IsAsync) -> + Options = case IsAsync of + true -> ?HTTP_CLIENT_OPTIONS ++ [{stream_to, self()}]; + false -> ?HTTP_CLIENT_OPTIONS + end, + JsonString = case Params of + [] -> Params; + _ -> jsx:encode(Params) + end, + ibrowse:send_req(Url, [?JSON_CONTENT], Method, JsonString, Options, ?HTTP_CLIENT_TIMEOUT). +%%%=================================================================== +%%% API +%%%=================================================================== +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +queue_request(Url, Method, Params) -> + gen_server:cast(?SERVER, {queue_request, Url, Method, Params, undefined}). + +queue_request(Url, Method, Params, CallBack) -> + gen_server:cast(?SERVER, {queue_request, Url, Method, Params, CallBack}). + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== +-record(request, {uuid, url, method, params, retry}). + +init([]) -> + erlang:send_after(?RESEND_AFTER_DELAY, self(), resend_all_requests), + {ok, #state{}}. + +handle_call(_Request, _From, State) -> + Reply = ok, + {reply, Reply, State}. + +handle_cast({queue_request, Url, Method, Params, CallBack}, State) -> + Uuid = uuid_factory:gen(), + Request = #request{uuid = Uuid, + url = Url, + method = Method, + params = Params, + retry = 0}, + shared_data:write(Request), + if + CallBack =:= undefined -> ok; + true -> game_counter:set({request, Uuid}, CallBack) + end, + do_send_request(Request), + {noreply, State}. + +handle_info(resend_all_requests, State) -> + ets:foldl(fun(Request, _) -> + do_send_request(Request) + end, unused, request), + {noreply, State}; +handle_info({resend_requst, Uuid}, State) -> + case shared_data:find(request, Uuid) of + undefined -> ok; + Request -> do_send_request(Request) + end, + {noreply, State}; +handle_info({ibrowse_async_headers, ReqId, Code, _Headers}, State) -> + %% error_logger:info_msg("handle_info:Response:~p, ~p~n", [ReqId, Code]), + put({code, ReqId}, Code), + {noreply, State}; +handle_info({ibrowse_async_response, ReqId, Response}, State) -> + Code = get({code, ReqId}), + Uuid = get({req_id, ReqId}), + %% error_logger:info_msg("handle_info:Response: ~p~n", [Response]), + if + Code =:= "200" -> + case game_counter:get({request, Uuid}) of + undefined -> ok; + CallBack -> + CallBack(Response), + game_counter:del({request, Uuid}) + end, + shared_data:find(request, Uuid), + erase({req_id, ReqId}), + erase({code, ReqId}); + true -> + case shared_data:find(request, Uuid) of + undefined -> ok; + Request -> + NewRequest = Request#request{retry = Request#request.retry + 1}, + Retry = NewRequest#request.retry, + if + Retry > ?MAX_RETRY -> + shared_data:delete(request, Uuid); + true -> + shared_data:write(NewRequest), + Delay = math:pow(2, Retry) * ?RESEND_AFTER_DELAY, + logger:info("Http Resend Dealy: ~p, Uuid: ~p~n", [Delay, Uuid]), + erlang:send_after(trunc(Delay), self(), {resend_requst, Uuid}) + end + end + end, + {noreply, State}; +handle_info({ibrowse_async_response_end, _ReqId}, State) -> + {noreply, State}; +handle_info(Msg, State) -> + error_logger:info_msg("handle_info Msg: ~p~n", [Msg]), + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +do_send_request(Request) -> + %% error_logger:info_msg("do_send_request: ~p", [Request]), + {ibrowse_req_id, _ReqId} = async_request(Request#request.url, + Request#request.method, + Request#request.params), + %% 临时处理 + shared_data:delete(request, Request#request.uuid). + %% put({req_id, ReqId}, Request#request.uuid). + %% 临时处理 + diff --git a/src/httpSocket/leo_http.erl b/src/httpSocket/leo_http.erl new file mode 100644 index 0000000..61f2dbe --- /dev/null +++ b/src/httpSocket/leo_http.erl @@ -0,0 +1,344 @@ +%%====================================================================== +%% +%% Leo Commons +%% +%% Copyright (c) 2012-2017 Rakuten, Inc. +%% +%% This file is provided to you 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. +%% +%% --------------------------------------------------------------------- +%% leo_http - Utils for HTTP/S3-API +%% +%% @doc leo_http is utilities for HTTP and S3-API +%% @reference https://github.com/leo-project/leo_commons/blob/master/src/leo_http.erl +%% @end +%%====================================================================== +-module(leo_http). + +-author('Yoshiyuki Kanno'). +-author('Yosuke Hara'). + +-export([key/2, key/3, + get_headers/2, get_headers/3, get_amz_headers/1, + get_headers4cow/2, get_headers4cow/3, get_amz_headers4cow/1, + rfc1123_date/1, web_date/1, url_encode/2 + ]). + +-include_lib("eunit/include/eunit.hrl"). + +-define(S3_DEFAULT_ENDPOINT, <<"s3.amazonaws.com">>). +-define(SLASH_BIN, <<"/">>). +-define(EMPYT_BIN, <<>>). + + +%% @doc Retrieve a filename(KEY) from Host and Path. +%% +-spec(key(Host, Path) -> + {binary(), binary()} when Host::binary(), + Path::binary()). +key(Host, Path) -> + key([?S3_DEFAULT_ENDPOINT], Host, Path). + +%% @doc Retrieve a filename(KEY) from Host and Path. +%% +-spec(key(EndPoints, Host, Path) -> + {binary(), binary()} when EndPoints::[binary()], + Host::binary(), + Path::binary()). +key(EndPoints, Host, Path) -> + EmptyBin = <<>>, + EndPoint = case lists:foldl( + fun(E, Acc) -> + case binary:match(Host, E) of + nomatch -> + Acc; + {_,_} -> + case (byte_size(Acc) < byte_size(E)) of + true -> E; + false -> Acc + end + end + end, EmptyBin, EndPoints) of + EmptyBin -> + []; + Ret -> + Ret + end, + key_1(EndPoint, Host, Path). + + +%% @doc Retrieve a filename(KEY) from Host and Path. +%% @private +key_1(EndPoint, Host, Path) -> + Index = case EndPoint of + [] -> + 0; + _ -> + case binary:match(Host, EndPoint) of + nomatch -> + 0; + {Pos, _} -> + Pos + 1 + end + end, + key_2(Index, Host, Path). + + +%% @doc "S3-Bucket" is equal to the host. +%% @private +key_2(0, Host, Path) -> + case binary:match(Path, [?SLASH_BIN]) of + nomatch -> + {Host, <>}; + _ -> + [_, Top|_] = binary:split(Path, [?SLASH_BIN], [global]), + case Top of + Host -> + Path2 = binary:part(Path, {1, byte_size(Path) -1}), + case binary:split(Path2, [?SLASH_BIN], [global]) of + [] -> + {?EMPYT_BIN, Path2}; + [Bucket|_] -> + {Bucket, Path2} + end; + _ -> + {Host, <>} + end + end; + +%% @doc "S3-Bucket" is included in the path +%% @private +key_2(1,_Host, ?SLASH_BIN) -> + {?EMPYT_BIN, ?SLASH_BIN}; + +key_2(1,_Host, Path) -> + case binary:match(Path, [?SLASH_BIN]) of + nomatch -> + {?EMPYT_BIN, ?SLASH_BIN}; + _ -> + Path2 = binary:part(Path, {1, byte_size(Path) -1}), + case binary:split(Path2, [?SLASH_BIN], [global]) of + [] -> + {?EMPYT_BIN, Path2}; + [Bucket|_] -> + {Bucket, Path2} + end + end; + +%% @doc "S3-Bucket" is included in the host +%% @private +key_2(Index, Host, Path) -> + Bucket = binary:part(Host, {0, Index -2}), + {Bucket, <>}. + + +%% @doc Retrieve AMZ-S3-related headers +%% assume that TreeHeaders is generated by mochiweb_header +%% +-spec(get_headers(TreeHeaders, FilterFun) -> + list() when TreeHeaders::gb_trees:tree(), + FilterFun::function()). +get_headers(TreeHeaders, FilterFun) when is_function(FilterFun) -> + Iter = gb_trees:iterator(TreeHeaders), + get_headers(Iter, FilterFun, []). + +%% @doc Retrieve AMZ-S3-related headers +%% assume that TreeHeaders is generated by mochiweb_header +%% +-spec(get_headers(TreeHeaders, FilterFun, Acc) -> + list() when TreeHeaders::gb_trees:iter(), + FilterFun::function(), + Acc::[any()] + ). +get_headers(Iter, FilterFun, Acc) -> + case gb_trees:next(Iter) of + none -> + Acc; + {Key, Val, Iter2} -> + case FilterFun(Key) of + true -> get_headers(Iter2, FilterFun, [Val|Acc]); + false -> get_headers(Iter2, FilterFun, Acc) + end + end. + + +%% @doc Retrieve headers for cowboy +%% +-spec(get_headers4cow(Headers, FilterFun) -> + list() when Headers::[any()], + FilterFun::function()). +get_headers4cow(Headers, FilterFun) when is_function(FilterFun) -> + get_headers4cow(Headers, FilterFun, []). + +%% @doc Retrieve headers for Cowboy +%% +-spec(get_headers4cow(Headers, FilterFun, Acc) -> + [{string(), string()}] when Headers::[{binary(), binary()}], + FilterFun::function(), + Acc::[any()]). +get_headers4cow([], _FilterFun, Acc) -> + Acc; +get_headers4cow([{K, V}|Rest], FilterFun, Acc) when is_binary(K) -> + case FilterFun(K) of + true -> + get_headers4cow(Rest, FilterFun, + [{binary_to_list(K), binary_to_list(V)}|Acc]); + false -> + get_headers4cow(Rest, FilterFun, Acc) + end; +get_headers4cow([_|Rest], FilterFun, Acc) -> + get_headers4cow(Rest, FilterFun, Acc). + + +%% @doc Retrieve AMZ-S3-related headers +%% +-spec(get_amz_headers(TreeHeaders) -> + list() when TreeHeaders::gb_trees:tree()). +get_amz_headers(TreeHeaders) -> + get_headers(TreeHeaders, fun is_amz_header/1). + +%% @doc Retrieve AMZ-S3-related headers for Cowboy +%% +-spec(get_amz_headers4cow(ListHeaders) -> + [{string(), string()}] when ListHeaders::[{binary(), binary()}]). +get_amz_headers4cow(ListHeaders) -> + get_headers4cow(ListHeaders, fun is_amz_header/1). + + +%% @doc Retrieve RFC-1123 formated data +%% +-spec(rfc1123_date(DateSec) -> + string() when DateSec::integer()). +rfc1123_date(DateSec) -> + %% NOTE: + %% Don't use http_util:rfc1123 on R14B*. + %% In this func, There is no error handling for `local_time_to_universe` + %% So badmatch could occur. This result in invoking huge context switched. + {{Y,M,D},{H,MI,S}} = calendar:gregorian_seconds_to_datetime(DateSec), + Mon = month(M), + W = weekday(Y,M,D), + lists:flatten( + io_lib:format("~3s, ~2.10.0B ~3s ~4.10B ~2.10.0B:~2.10.0B:~2.10.0B GMT", + [W, D, Mon, Y, H, MI, S])). + + +%% @doc Convert gregorian seconds to date formated data( YYYY-MM-DDTHH:MI:SS000Z ) +%% +-spec(web_date(GregSec) -> + string() when GregSec::integer()). +web_date(GregSec) when is_integer(GregSec) -> + {{Y,M,D},{H,MI,S}} = calendar:gregorian_seconds_to_datetime(GregSec), + lists:flatten(io_lib:format("~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~2.10.0B.000Z",[Y,M,D,H,MI,S])). + + +%%-------------------------------------------------------------------- +%%% INTERNAL FUNCTIONS +%%-------------------------------------------------------------------- +%% @doc Is it AMZ-S3's header? +%% @private +-spec(is_amz_header(string()|binary()) -> + boolean()). +is_amz_header(<<"x-amz-", _Rest/binary>>) -> true; +is_amz_header(<<"X-Amz-", _Rest/binary>>) -> true; +is_amz_header(Key) when is_binary(Key) -> + false; +is_amz_header(Key) -> + (string:str(string:to_lower(Key), "x-amz-") == 1). + + +%% @doc Get weekday as string +%% @private +-spec(weekday(pos_integer(), pos_integer(), pos_integer()) -> + string()). +weekday(Y, M, D) -> + weekday(calendar:day_of_the_week(Y, M, D)). +weekday(1) -> "Mon"; +weekday(2) -> "Tue"; +weekday(3) -> "Wed"; +weekday(4) -> "Thu"; +weekday(5) -> "Fri"; +weekday(6) -> "Sat"; +weekday(7) -> "Sun". + + +%% @doc Get month as string +%% @private +-spec(month(pos_integer()) -> + string()). +month( 1) -> "Jan"; +month( 2) -> "Feb"; +month( 3) -> "Mar"; +month( 4) -> "Apr"; +month( 5) -> "May"; +month( 6) -> "Jun"; +month( 7) -> "Jul"; +month( 8) -> "Aug"; +month( 9) -> "Sep"; +month(10) -> "Oct"; +month(11) -> "Nov"; +month(12) -> "Dec". + +%% @doc URL encode a string binary. +%% The `noplus' option disables the default behaviour of quoting space +%% characters, `\s', as `+'. The `upper' option overrides the default behaviour +%% of writing hex numbers using lowecase letters to using uppercase letters +%% instead. +-spec url_encode(binary(), [noplus|upper|noslash]) -> binary(). +url_encode(Bin, Opts) -> + Plus = not lists:member(noplus, Opts), + Upper = lists:member(upper, Opts), + Slash = not lists:member(noslash, Opts), + url_encode(Bin, <<>>, Plus, Upper, Slash). + +-spec url_encode(binary(), binary(), boolean(), boolean(), boolean()) -> binary(). +url_encode(<>, Acc, P=Plus, U=Upper, S=Slash) -> + if C >= $0, + C =< $9 -> + url_encode(Rest, <>, P, U, S); + C >= $A, + C =< $Z -> + url_encode(Rest, <>, P, U, S); + C >= $a, + C =< $z -> + url_encode(Rest, <>, P, U, S); + C =:= $.; + C =:= $-; + C =:= $~; + C =:= $_ -> + url_encode(Rest, <>, P, U, S); + + C =:= $/ , not Slash -> + url_encode(Rest, <>, P, U, S); + C =:= $ , Plus -> + url_encode(Rest, <>, P, U, S); + + true -> + H = C band 16#F0 bsr 4, + L = C band 16#0F, + H1 = if Upper -> to_hexu(H); true -> to_hexl(H) end, + L1 = if Upper -> to_hexu(L); true -> to_hexl(L) end, + url_encode(Rest, <>, P, U, S) + end; +url_encode(<<>>, Acc, _Plus, _Upper, _Slash) -> + Acc. + +-spec to_hexu(byte()) -> byte(). +to_hexu(C) when C < 10 -> $0 + C; +to_hexu(C) when C < 17 -> $A + C - 10. + +-spec to_hexl(byte()) -> byte(). +to_hexl(C) when C < 10 -> $0 + C; +to_hexl(C) when C < 17 -> $a + C - 10. + diff --git a/src/httpSocket/utHttpOn.erl b/src/httpSocket/utHttpOn.erl new file mode 100644 index 0000000..e2a74e6 --- /dev/null +++ b/src/httpSocket/utHttpOn.erl @@ -0,0 +1,55 @@ +-module(utHttpOn). + +%% API +-export([]). + +%% API +-export([ + req/6, %% Post, Put, Delete Only + req_get/3, %% Get Only + url_encode/1 +]). + +-export([get_unique_ref/1, get_conv_ref/0]). + +get_conv_ref() -> + get_unique_ref(20). +get_unique_ref(Length) -> + IntPass = random_num(36, Length), + list_to_binary(lists:flatten(io_lib:format("~.36B", [IntPass]))). + +random_num(NumeralSystemBase, Length) -> + Min = round(math:pow(NumeralSystemBase, Length - 1)), + Max = round(math:pow(NumeralSystemBase, Length)), + crypto:rand_uniform(Min, Max). + +req(Method, Url, Headers, ContType, Body, HttpOpts) + when Method == post orelse Method == put orelse Method == delete -> + HttpClOpts = [{sync, true}, {body_format, binary}], + Resp = httpc:request(Method, {eu_types:to_list(Url), Headers, ContType, Body}, HttpOpts, HttpClOpts), + minimize_resp(Resp). + +req_get(Url, Headers, HttpOpts) -> + HttpClOpts = [{sync, true}, {body_format, binary}], + Resp = httpc:request(get, {eu_types:to_list(Url), Headers}, HttpOpts, HttpClOpts), + minimize_resp(Resp). + +minimize_resp(Resp) -> + case Resp of + {ok, {{_NewVrsn, 200, _}, _Headers, RespBody}} -> + {ok, 200, RespBody}; + {ok, {{_NewVrsn, HttpCode, _}, _Headers, RespBody}} -> + {error, HttpCode, RespBody}; + Any -> Any + end. + +url_encode(Data) -> + url_encode(Data, ""). + +url_encode([], Acc) -> + Acc; + +url_encode([{Key, Value} | R], "") -> + url_encode(R, edoc_lib:escape_uri(Key) ++ "=" ++ edoc_lib:escape_uri(Value)); +url_encode([{Key, Value} | R], Acc) -> + url_encode(R, Acc ++ "&" ++ edoc_lib:escape_uri(Key) ++ "=" ++ edoc_lib:escape_uri(Value)). \ No newline at end of file diff --git a/src/httpSocket/utHttpUrl.erl b/src/httpSocket/utHttpUrl.erl new file mode 100644 index 0000000..6ab9da0 --- /dev/null +++ b/src/httpSocket/utHttpUrl.erl @@ -0,0 +1,88 @@ +-module(utHttpUrl). + +%% API +-export([]). +-export([ + url_encode_request_body/1, + url_encode/1, + integer_to_hex/1, + get_ip_from_headers/2, + get_x_real_ip/1 +]). + + +url_encode_request_body(Args) -> + lists:concat( + lists:foldl( + fun(Rec, []) -> [Rec]; (Rec, Ac) -> [Rec, "&" | Ac] end, + [], + [K ++ "=" ++ url_encode(V) || {K, V} <- Args] + ) + ). + + +%% encode url params +url_encode(T) when is_binary(T) -> + binary_to_list(T); +url_encode([H | T]) -> + if + H >= $a, $z >= H -> + [H | url_encode(T)]; + H >= $A, $Z >= H -> + [H | url_encode(T)]; + H >= $0, $9 >= H -> + [H | url_encode(T)]; + H == $_; H == $.; H == $-; H == $/; H == $: -> % FIXME: more.. + [H | url_encode(T)]; + true -> + case integer_to_hex(H) of + [X, Y] -> + [$%, X, Y | url_encode(T)]; + [X] -> + [$%, $0, X | url_encode(T)] + end + end; + +url_encode([]) -> []. + +integer_to_hex(I) -> + case catch erlang:integer_to_list(I, 16) of + {'EXIT', _} -> + old_integer_to_hex(I); + Int -> + Int + end. +old_integer_to_hex(I) when I < 10 -> + integer_to_list(I); +old_integer_to_hex(I) when I < 16 -> + [I - 10 + $A]; +old_integer_to_hex(I) when I >= 16 -> + N = trunc(I / 16), + old_integer_to_hex(N) ++ old_integer_to_hex(I rem 16). + +get_ip_from_headers(PeerIp, Headers) -> + RealIp = proplists:get_value(<<"x-real-ip">>, Headers), + ForwardedForRaw = proplists:get_value(<<"x-forwarded-for">>, Headers), + + ForwardedFor = case ForwardedForRaw of + undefined -> undefined; + ForwardedForRaw -> + case re:run(ForwardedForRaw, "^(?[^\\,]+)", + [{capture, [first_ip], binary}]) of + {match, [FirstIp]} -> FirstIp; + _Any -> undefined + end + end, + {ok, PeerAddr} = if + is_binary(RealIp) -> inet_parse:address(binary_to_list(RealIp)); + is_binary(ForwardedFor) -> inet_parse:address(binary_to_list(ForwardedFor)); + true -> {ok, PeerIp} + end, + PeerAddr. + + + + +get_x_real_ip(Headers) -> + RealIp = proplists:get_value(<<"x-real-ip">>, Headers), + RealIp. \ No newline at end of file diff --git a/src/httpSocket/utSocket.erl b/src/httpSocket/utSocket.erl new file mode 100644 index 0000000..65a800a --- /dev/null +++ b/src/httpSocket/utSocket.erl @@ -0,0 +1,29 @@ +-module(utSocket). + +-export([ + socket2ip/1 + , socket2port/1 + , str2ip/1 +]). + + +%% socket转IP +socket2ip(Socket) -> + case inet:peername(Socket) of + {ok, {{A, B, C, D}, _}} -> + string:join(lists:map(fun com_type:to_list/1, [A, B, C, D]), "."); + _ -> + "" + end. + +socket2port(Socket) -> + case inet:peername(Socket) of + {ok, {_, Port}} -> Port; + _ -> 0 + end. + +str2ip(IP) when is_list(IP) -> + [A1, A2, A3, A4] = string:tokens(IP, "."), + {list_to_integer(A1), list_to_integer(A2), list_to_integer(A3), list_to_integer(A4)}; +str2ip(IP) when is_tuple(IP) -> + IP. \ No newline at end of file diff --git a/src/timeDate/utTime.erl b/src/timeDate/utTime.erl index 2b86e2f..9c70354 100644 --- a/src/timeDate/utTime.erl +++ b/src/timeDate/utTime.erl @@ -556,21 +556,29 @@ lMonthName(11) -> <<"November">>; lMonthName(12) -> <<"December">>. %% 年月日的数字version --spec dateNum() -> integer(). -dateNum() -> +-spec dateNumber() -> integer(). +dateNumber() -> {Year, Month, Day} = erlang:date(), Year * 10000 + Month * 100 + Day. %% 年月日的数字version --spec dateNum(date()) -> integer(). -dateNum({Year, Month, Day}) -> +-spec dateNumber(date()) -> integer(). +dateNumber({Year, Month, Day}) -> Year * 10000 + Month * 100 + Day. %% 年月日的数字version --spec dateNum(Year :: year(), Month :: month(), Day :: day()) -> integer(). -dateNum(Year, Month, Day) -> +-spec dateNumber(Year :: year(), Month :: month(), Day :: day()) -> integer(). +dateNumber(Year, Month, Day) -> Year * 10000 + Month * 100 + Day. +%% dateNumber 反转 +-spec numberDate(DateNumber :: integer()) -> date(). +numberDate(DateNumber) -> + Y = DateNumber div 10000, + M = DateNumber rem 10000 div 100, + D = DateNumber rem 100, + {Y, M, D}. + %% 秒转为天数 和 time() -spec secToDayTime(Secs :: timestamp()) -> {Day :: integer(), Time :: time()}. secToDayTime(Secs) ->