erlang各种有用的函数包括一些有用nif封装,还有一些性能测试case。
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

348 строки
16 KiB

5 лет назад
5 лет назад
5 лет назад
  1. %%%-------------------------------------------------------------------
  2. %%% @author root
  3. %%% @copyright (C) 2015, <COMPANY>
  4. %%% @doc
  5. %%%
  6. %%% @end
  7. %%% Created : 30. 五月 2015 下午8:32
  8. %%%-------------------------------------------------------------------
  9. %% Copyright (c) 2007
  10. %% Mats Cronqvist <mats.cronqvist@ericsson.com>
  11. %% Chris Newcombe <chris.newcombe@gmail.com>
  12. %% Jacob Vorreuter <jacob.vorreuter@gmail.com>
  13. %%
  14. %% Permission is hereby granted, free of charge, to any person
  15. %% obtaining a copy of this software and associated documentation
  16. %% files (the "Software"), to deal in the Software without
  17. %% restriction, including without limitation the rights to use,
  18. %% copy, modify, merge, publish, distribute, sublicense, and/or sell
  19. %% copies of the Software, and to permit persons to whom the
  20. %% Software is furnished to do so, subject to the following
  21. %% conditions:
  22. %%
  23. %% The above copyright notice and this permission notice shall be
  24. %% included in all copies or substantial portions of the Software.
  25. %%
  26. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  27. %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  28. %% OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  29. %% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  30. %% HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  31. %% WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  32. %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  33. %% OTHER DEALINGS IN THE SOFTWARE.
  34. %%%-------------------------------------------------------------------
  35. %%% File : dynamic_compile.erl
  36. %%% Description :
  37. %%% Authors : Mats Cronqvist <mats.cronqvist@ericsson.com>
  38. %%% Chris Newcombe <chris.newcombe@gmail.com>
  39. %%% Jacob Vorreuter <jacob.vorreuter@gmail.com>
  40. %%% TODO :
  41. %%% - add support for limit include-file depth (and prevent circular references)
  42. %%% prevent circular macro expansion set FILE correctly when -module() is found
  43. %%% -include_lib support $ENVVAR in include filenames
  44. %%% substitute-stringize (??MACRO)
  45. %%% -undef/-ifdef/-ifndef/-else/-endif
  46. %%% -file(File, Line)
  47. %%%-------------------------------------------------------------------
  48. -module(dynamic_compile).
  49. %% API
  50. -export([init/0, load_from_string/1, load_from_string/2]).
  51. -export([from_string/1, from_string/2, get_forms_from_src/1]).
  52. -import(lists, [reverse/1, keyreplace/4]).
  53. -export([module_src/1, module_lt/1, modules/0, module_new/2]).
  54. -record(module, {name = 0, loadtime = 0, src}).
  55. init() ->
  56. ets:new(?MODULE, [public, set, named_table, {keypos, #module.name}]),
  57. ok.
  58. module_src(Module) ->
  59. init_if_notinit(),
  60. case ets:lookup(?MODULE, Module) of
  61. [] -> false;
  62. [#module{src = Src}] -> Src
  63. end.
  64. module_lt(Module) ->
  65. init_if_notinit(),
  66. case ets:lookup(?MODULE, Module) of
  67. [] -> false;
  68. [#module{loadtime = Lt}] -> Lt
  69. end.
  70. modules() ->
  71. init_if_notinit(),
  72. ets:tab2list(?MODULE).
  73. module_new(Module, Src) ->
  74. ets:insert(?MODULE, #module{name = Module, src = Src}).
  75. init_if_notinit() ->
  76. case ets:info(?MODULE) of
  77. undefined ->
  78. init();
  79. _ ->
  80. ignore
  81. end.
  82. %%====================================================================
  83. %% API
  84. %%====================================================================
  85. %%--------------------------------------------------------------------
  86. %% Function:
  87. %% Description:
  88. %% Compile module from string and load into VM
  89. %%--------------------------------------------------------------------
  90. load_from_string(CodeStr) ->
  91. load_from_string(CodeStr, []).
  92. load_from_string(CodeStr, CompileFormsOptions) ->
  93. {Mod, Bin} = from_string(CodeStr, CompileFormsOptions),
  94. code:load_binary(Mod, [], Bin),
  95. ok.
  96. %% module_new(Mod, CodeStr).
  97. %%--------------------------------------------------------------------
  98. %% Function:
  99. %% Description:
  100. %% Returns a binary that can be used with
  101. %% code:load_binary(Module, ModuleFilenameForInternalRecords, Binary).
  102. %%--------------------------------------------------------------------
  103. from_string(CodeStr) ->
  104. from_string(CodeStr, []).
  105. % takes Options as for compile:forms/2
  106. from_string(CodeStr, CompileFormsOptions) ->
  107. %% Initialise the macro dictionary with the default predefined macros,
  108. %% (adapted from epp.erl:predef_macros/1
  109. Filename = "compiled_from_string",
  110. %%Machine = list_to_atom(erlang:system_info(machine)),
  111. Ms0 = dict:new(),
  112. % Ms1 = dict:store('FILE', {[], "compiled_from_string"}, Ms0),
  113. % Ms2 = dict:store('LINE', {[], 1}, Ms1), % actually we might add special code for this
  114. % Ms3 = dict:store('MODULE', {[], undefined}, Ms2),
  115. % Ms4 = dict:store('MODULE_STRING', {[], undefined}, Ms3),
  116. % Ms5 = dict:store('MACHINE', {[], Machine}, Ms4),
  117. % InitMD = dict:store(Machine, {[], true}, Ms5),
  118. InitMD = Ms0,
  119. %% From the docs for compile:forms:
  120. %% When encountering an -include or -include_dir directive, the compiler searches for header files in the following directories:
  121. %% 1. ".", the current working directory of the file server;
  122. %% 2. the base name of the compiled file;
  123. %% 3. the directories specified using the i option. The directory specified last is searched first.
  124. %% In this case, #2 is meaningless.
  125. IncludeSearchPath = ["." | reverse([Dir || {i, Dir} <- CompileFormsOptions])],
  126. {RevForms, _OutMacroDict} = scan_and_parse(CodeStr, Filename, 1, [], InitMD, IncludeSearchPath),
  127. Forms = [{attribute, 0, file, {"compiled_from_string", 0}} | reverse([{eof, 0} | RevForms])],
  128. %% note: 'binary' is forced as an implicit option, whether it is provided or not.
  129. case compile:forms(Forms, CompileFormsOptions) of
  130. {ok, ModuleName, CompiledCodeBinary} when is_binary(CompiledCodeBinary) ->
  131. {ModuleName, CompiledCodeBinary};
  132. {ok, ModuleName, CompiledCodeBinary, []} when is_binary(CompiledCodeBinary) -> % empty warnings list
  133. {ModuleName, CompiledCodeBinary};
  134. {ok, _ModuleName, _CompiledCodeBinary, Warnings} ->
  135. throw({?MODULE, warnings, Warnings});
  136. Other ->
  137. throw({?MODULE, compile_forms, Other})
  138. end.
  139. get_forms_from_src(Src) ->
  140. case scan_and_parse(Src, none, 1, [], dict:new(), []) of
  141. {RevForms, _} -> [{attribute, 0, file, {"compiled_from_string", 0}} | reverse([{eof, 0} | RevForms])]
  142. end.
  143. %%====================================================================
  144. %% Internal functions
  145. %%====================================================================
  146. %%% Code from Mats Cronqvist
  147. %%% See http://www.erlang.org/pipermail/erlang-questions/2007-March/025507.html
  148. %%%## 'scan_and_parse'
  149. %%%
  150. %%% basically we call the OTP scanner and parser (erl_scan and
  151. %%% erl_parse) line-by-line, but check each scanned line for (or
  152. %%% definitions of) macros before parsing.
  153. %% returns {ReverseForms, FinalMacroDict}
  154. scan_and_parse([], _CurrFilename, _CurrLine, RevForms, MacroDict, _IncludeSearchPath) ->
  155. {RevForms, MacroDict};
  156. scan_and_parse(RemainingText, CurrFilename, CurrLine, RevForms, MacroDict, IncludeSearchPath) ->
  157. case scanner(RemainingText, CurrLine, MacroDict) of
  158. {tokens, NLine, NRemainingText, Toks} ->
  159. {ok, Form0} = erl_parse:parse_form(Toks),
  160. {Form, Forms} = normalize_record(Form0),
  161. scan_and_parse(NRemainingText, CurrFilename, NLine, [Form | Forms ++ RevForms], MacroDict, IncludeSearchPath);
  162. {macro, NLine, NRemainingText, NMacroDict} ->
  163. scan_and_parse(NRemainingText, CurrFilename, NLine, RevForms, NMacroDict, IncludeSearchPath);
  164. {include, NLine, NRemainingText, IncludeFilename} ->
  165. IncludeFileRemainingTextents = read_include_file(IncludeFilename, IncludeSearchPath),
  166. %%io:format("include file ~p contents: ~n~p~nRemainingText = ~p~n", [IncludeFilename,IncludeFileRemainingTextents, RemainingText]),
  167. %% Modify the FILE macro to reflect the filename
  168. %%IncludeMacroDict = dict:store('FILE', {[],IncludeFilename}, MacroDict),
  169. IncludeMacroDict = MacroDict,
  170. %% Process the header file (inc. any nested header files)
  171. {RevIncludeForms, IncludedMacroDict} = scan_and_parse(IncludeFileRemainingTextents, IncludeFilename, 1, [], IncludeMacroDict, IncludeSearchPath),
  172. %io:format("include file results = ~p~n", [R]),
  173. %% Restore the FILE macro in the NEW MacroDict (so we keep any macros defined in the header file)
  174. %%NMacroDict = dict:store('FILE', {[],CurrFilename}, IncludedMacroDict),
  175. NMacroDict = IncludedMacroDict,
  176. %% Continue with the original file
  177. scan_and_parse(NRemainingText, CurrFilename, NLine, RevIncludeForms ++ RevForms, NMacroDict, IncludeSearchPath);
  178. done ->
  179. scan_and_parse([], CurrFilename, CurrLine, RevForms, MacroDict, IncludeSearchPath)
  180. end.
  181. scanner(Text, Line, MacroDict) ->
  182. case erl_scan:tokens([], Text, Line) of
  183. {done, {ok, Toks, NLine}, LeftOverChars} ->
  184. case pre_proc(Toks, MacroDict) of
  185. {tokens, NToks} -> {tokens, NLine, LeftOverChars, NToks};
  186. {macro, NMacroDict} -> {macro, NLine, LeftOverChars, NMacroDict};
  187. {include, Filename} -> {include, NLine, LeftOverChars, Filename}
  188. end;
  189. {more, _Continuation} ->
  190. %% This is supposed to mean "term is not yet complete" (i.e. a '.' has
  191. %% not been reached yet).
  192. %% However, for some bizarre reason we also get this if there is a comment after the final '.' in a file.
  193. %% So we check to see if Text only consists of comments.
  194. case is_only_comments(Text) of
  195. true ->
  196. done;
  197. false ->
  198. throw({incomplete_term, _Continuation, Text, Line})
  199. end
  200. end.
  201. is_only_comments(Text) -> is_only_comments(Text, not_in_comment).
  202. is_only_comments([], _) -> true;
  203. is_only_comments([$ | T], not_in_comment) ->
  204. is_only_comments(T, not_in_comment); % skipping whitspace outside of comment
  205. is_only_comments([$\t | T], not_in_comment) ->
  206. is_only_comments(T, not_in_comment); % skipping whitspace outside of comment
  207. is_only_comments([$\n | T], not_in_comment) ->
  208. is_only_comments(T, not_in_comment); % skipping whitspace outside of comment
  209. is_only_comments([$% | T], not_in_comment) -> is_only_comments(T, in_comment); % found start of a comment
  210. is_only_comments(_, not_in_comment) -> false;
  211. % found any significant char NOT in a comment
  212. is_only_comments([$\n | T], in_comment) -> is_only_comments(T, not_in_comment); % found end of a comment
  213. is_only_comments([_ | T], in_comment) -> is_only_comments(T, in_comment). % skipping over in-comment chars
  214. %%%## 'pre-proc'
  215. %%%
  216. %%% have to implement a subset of the pre-processor, since epp insists
  217. %%% on running on a file.
  218. %%% only handles 2 cases;
  219. %% -define(MACRO, something).
  220. %% -define(MACRO(VAR1,VARN),{stuff,VAR1,more,stuff,VARN,extra,stuff}).
  221. pre_proc([{'-', _}, {atom, _, define}, {'(', _}, {_, _, Name} | DefToks], MacroDict) ->
  222. false = dict:is_key(Name, MacroDict),
  223. case DefToks of
  224. [{',', _} | Macro] ->
  225. {macro, dict:store(Name, {[], macro_body_def(Macro, [])}, MacroDict)};
  226. [{'(', _} | Macro] ->
  227. {macro, dict:store(Name, macro_params_body_def(Macro, []), MacroDict)}
  228. end;
  229. pre_proc([{'-', _}, {atom, _, include}, {'(', _}, {string, _, Filename}, {')', _}, {dot, _}], _MacroDict) ->
  230. {include, Filename};
  231. pre_proc(Toks, MacroDict) ->
  232. {tokens, subst_macros(Toks, MacroDict)}.
  233. macro_params_body_def([{')', _}, {',', _} | Toks], RevParams) ->
  234. {reverse(RevParams), macro_body_def(Toks, [])};
  235. macro_params_body_def([{var, _, Param} | Toks], RevParams) ->
  236. macro_params_body_def(Toks, [Param | RevParams]);
  237. macro_params_body_def([{',', _}, {var, _, Param} | Toks], RevParams) ->
  238. macro_params_body_def(Toks, [Param | RevParams]).
  239. macro_body_def([{')', _}, {dot, _}], RevMacroBodyToks) ->
  240. reverse(RevMacroBodyToks);
  241. macro_body_def([Tok | Toks], RevMacroBodyToks) ->
  242. macro_body_def(Toks, [Tok | RevMacroBodyToks]).
  243. subst_macros(Toks, MacroDict) ->
  244. reverse(subst_macros_rev(Toks, MacroDict, [])).
  245. %% returns a reversed list of tokes
  246. subst_macros_rev([{'?', _}, {_, LineNum, 'LINE'} | Toks], MacroDict, RevOutToks) ->
  247. %% special-case for ?LINE, to avoid creating a new MacroDict for every line in the source file
  248. subst_macros_rev(Toks, MacroDict, [{integer, LineNum, LineNum}] ++ RevOutToks);
  249. subst_macros_rev([{'?', _}, {_, _, Name}, {'(', _} = Paren | Toks], MacroDict, RevOutToks) ->
  250. case dict:fetch(Name, MacroDict) of
  251. {[], MacroValue} ->
  252. %% This macro does not have any vars, so ignore the fact that the invocation is followed by "(...stuff"
  253. %% Recursively expand any macro calls inside this macro's value
  254. %% TODO: avoid infinite expansion due to circular references (even indirect ones)
  255. RevExpandedOtherMacrosToks = subst_macros_rev(MacroValue, MacroDict, []),
  256. subst_macros_rev([Paren | Toks], MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks);
  257. ParamsAndBody ->
  258. %% This macro does have vars.
  259. %% Collect all of the passe arguments, in an ordered list
  260. {NToks, Arguments} = subst_macros_get_args(Toks, []),
  261. %% Expand the varibles
  262. ExpandedParamsToks = subst_macros_subst_args_for_vars(ParamsAndBody, Arguments),
  263. %% Recursively expand any macro calls inside this macro's value
  264. %% TODO: avoid infinite expansion due to circular references (even indirect ones)
  265. RevExpandedOtherMacrosToks = subst_macros_rev(ExpandedParamsToks, MacroDict, []),
  266. subst_macros_rev(NToks, MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks)
  267. end;
  268. subst_macros_rev([{'?', _}, {_, _, Name} | Toks], MacroDict, RevOutToks) ->
  269. %% This macro invocation does not have arguments.
  270. %% Therefore the definition should not have parameters
  271. {[], MacroValue} = dict:fetch(Name, MacroDict),
  272. %% Recursively expand any macro calls inside this macro's value
  273. %% TODO: avoid infinite expansion due to circular references (even indirect ones)
  274. RevExpandedOtherMacrosToks = subst_macros_rev(MacroValue, MacroDict, []),
  275. subst_macros_rev(Toks, MacroDict, RevExpandedOtherMacrosToks ++ RevOutToks);
  276. subst_macros_rev([Tok | Toks], MacroDict, RevOutToks) ->
  277. subst_macros_rev(Toks, MacroDict, [Tok | RevOutToks]);
  278. subst_macros_rev([], _MacroDict, RevOutToks) -> RevOutToks.
  279. subst_macros_get_args([{')', _} | Toks], RevArgs) ->
  280. {Toks, reverse(RevArgs)};
  281. subst_macros_get_args([{',', _}, {var, _, ArgName} | Toks], RevArgs) ->
  282. subst_macros_get_args(Toks, [ArgName | RevArgs]);
  283. subst_macros_get_args([{var, _, ArgName} | Toks], RevArgs) ->
  284. subst_macros_get_args(Toks, [ArgName | RevArgs]).
  285. subst_macros_subst_args_for_vars({[], BodyToks}, []) ->
  286. BodyToks;
  287. subst_macros_subst_args_for_vars({[Param | Params], BodyToks}, [Arg | Args]) ->
  288. NBodyToks = keyreplace(Param, 3, BodyToks, {var, 1, Arg}),
  289. subst_macros_subst_args_for_vars({Params, NBodyToks}, Args).
  290. read_include_file(Filename, IncludeSearchPath) ->
  291. case file:path_open(IncludeSearchPath, Filename, [read, raw, binary]) of
  292. {ok, IoDevice, FullName} ->
  293. {ok, Data} = file:read(IoDevice, filelib:file_size(FullName)),
  294. file:close(IoDevice),
  295. binary_to_list(Data);
  296. {error, Reason} ->
  297. throw({failed_to_read_include_file, Reason, Filename, IncludeSearchPath})
  298. end.
  299. normalize_record({attribute, La, record, {Record, Fields}} = Form) ->
  300. case epp:normalize_typed_record_fields(Fields) of
  301. {typed, NewFields} ->
  302. {{attribute, La, record, {Record, NewFields}},
  303. [{attribute, La, type,
  304. {{record, Record}, Fields, []}}]};
  305. not_typed ->
  306. {Form, []}
  307. end;
  308. normalize_record(Form) ->
  309. {Form, []}.