├── .gitignore ├── Makefile ├── README.md ├── bin ├── rebar └── rebar.linux └── src ├── spt_atom.erl ├── spt_cast.erl ├── spt_cast_sup.erl ├── spt_config.erl ├── spt_crate.app.src ├── spt_crate_app.erl ├── spt_crate_sup.erl ├── spt_ets.erl ├── spt_notify.erl ├── spt_payload.erl ├── spt_reloader.erl ├── spt_server.erl ├── spt_smerl.erl └── spt_time.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .eunit 2 | erl_crash.dump 3 | ebin/* 4 | include/proto_api.hrl 5 | include/proto_record.hrl 6 | include/proto_error.hrl 7 | src/proto_ack.erl 8 | src/proto_encoder.erl 9 | src/proto_decoder.erl 10 | src/proto_error.erl 11 | src/proto_indian.hrl 12 | src/proto_request.erl 13 | deps/* 14 | .rebar/ -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 需要加载的代码路径 2 | LOAD_PATH = \ 3 | ebin \ 4 | deps/*/ebin \ 5 | $(NULL) 6 | 7 | # 节点名称 8 | NODE = galaxy-empire@127.0.0.1 9 | # cookie 10 | COOKIE = abcdeft 11 | 12 | # 部分配置参数 13 | OPTS = \ 14 | -pa $(LOAD_PATH) \ 15 | -env ERL_MAX_ETS_TABLES 10000 \ 16 | -setcookie $(COOKIE) \ 17 | +A 8 +K true +P 120000 # -smp disable \ 18 | -detached \ 19 | -noshell \ 20 | $(NULL) 21 | 22 | # rebar-用于编译 23 | REBAR := ./bin/rebar --config config/rebar.config 24 | UNAME := $(shell uname) 25 | ifeq ($(UNAME), Linux) 26 | REBAR := ./bin/rebar.linux --config config/rebar.config 27 | # do something Linux-y 28 | endif 29 | 30 | # 编译全部 31 | all: 32 | $(REBAR) compile 33 | 34 | # 获取到所有的依赖 35 | deps: 36 | $(REBAR) get-deps 37 | 38 | t: 39 | $(REBAR) compile eunit 40 | 41 | c: 42 | $(REBAR) clean 43 | 44 | # 调用生成器生成代码 45 | g: 46 | cd src/ && ruby ./proto_gen.rb 47 | 48 | s: 49 | erl $(OPTS) -name $(NODE) -s slg_support start 50 | 51 | e: 52 | erl $(OPTS) 53 | 54 | r: 55 | erl $(OPTS) -s robot start 56 | 57 | .PHONY: deps get-deps 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 本组件提供一些erlang编程或系统工具集合。 2 | 3 | * spt_reloader-热更新 4 | * spt_notify-事件注册/分发 5 | * spt_smerl-动态模块编程库 6 | * spt_cast-进程组广播 7 | 8 | ### 1.spt_reloader-热更新 9 | 10 | 热更新进程,来源于michi-web,可以对线上运行的代码进行热替换,但应该遵守以下面规则: 11 | 12 | * 新代码必须没有改变上下文数据结构,才能进行热部署. 13 | * 有一个以上模块被修改,模块被reload的顺序不可保证,因此,如果你的函数原型发生了变化,或者调用了新增的函数,热部署都很危险。 14 | * 对在单个模块代码bug的hotfix,非常适合直接reloader。 15 | 16 | 默认情况下spt_reloader启动,当beam代码发生变化时将会自动热更新。 17 | 18 | ### 2.spt_notify-事件注册/分发. 19 | 20 | 游戏中需要关注很多事件的发生,比如建筑升级事件,玩家打赢了一个boss事件,spt_notify提供事件的注册和发生接口,有以下3个api: 21 | 22 | * sub(Event, Fun):订阅 23 | * unSub(Event, Fun):取消订阅 24 | * post(Event, Param):事件发生. 25 | 26 | post事件的第2个参数将会被原样传递给注册的函数,使用例子如下: 27 | 28 | Fun1 = fun(X) -> io:format("x1 ~p~n", [X]) end, 29 | Fun2 = fun(X) -> io:format("x2 ~p~n", [X]) end, 30 | spt_notify:sub(e1, Fun1), 31 | spt_notify:sub(e1, Fun2), 32 | spt_notify:post(e1, 23), 33 | spt_notify:ubsub(e1, Fun1), 34 | spt_notify:post(e1, 23), 35 | 36 | 37 | ### 3.spt_smerl-动态模块编程库 38 | 39 | 虽然erlang的动态编程能力不强(也或者是我学的很浅),但是smerl这个模块用来做动态模块扩展是比较合适的,它来源于erlyweb项目,已经稳定了很多年。 40 | 41 | 以下情况适合使用smerl动态产生模块: 42 | 43 | * 大量的重复编程模块:比如slg_model里的表model,基本结构都一样(select, update, delete),只有一点参数的不同而已。 44 | * 环境参数:有的环境参数,如果放在ets表又太慢了,放在固定的模块在每次修改时又需要编译,所以我倾向于动态产生一个模块,然后从模块函数里直接获取配置参数。 45 | 46 | 使用方法如下,来源于源码注释: 47 | 48 | test_smerl() -> 49 | M1 = spt_smerl:new(foo), 50 | {ok, M2} = spt_smerl:add_func(M1, "bar() -> 1 + 1."), 51 | spt_smerl:compile(M2), 52 | foo:bar(), % returns 2`` 53 | spt_smerl:has_func(M2, bar, 0). % returns true 54 | 55 | 56 | ### 4.spt_cast-进程组广播 57 | 58 | 该模块用来给一组进程发送广播信号,可支持动态的添加和删除进程;开放以下几个api: 59 | 60 | * join(Atom, Pid):加入广播进程组,Atom为组播进程的注册原子,PID为想要加入的进程ID; 61 | * quit(Atom, Pid):退出广播进程组,参数同上。 62 | * send(Atom, Msg):向某个进程组广播消息Msg,此消息将会被立即广播到其组内的其他组员。 63 | * stop(Atom):让组播进程退出。 64 | 65 | 使用例子如下: 66 | 67 | spt_cast_sup:start_caster(test), 68 | spt_cast:join(test, Pid1),% 同步 69 | spt_cast:send(test, "test"), % 同步 70 | spt_cast:quit(test, Pid1).% 同步 71 | spt_cast:cast(test, "test"),% 异步 72 | 73 | * 聊天:在游戏服务器中有大量的组播操作,比如全局聊天频道,程序启动可以用 `spt_cast_sup:start_caster(chat_all)`开启一个组播进程,然后让每个登陆的进程都加入它,之后便可以通过spt_cast:send(chat_all, ChatMsg)发送聊天消息。 74 | * 场景:当玩家进程一个游戏场景时加入广播组,然后实时发送它的位置信息。 75 | * 全服广播:现在服务器中玩家获取到一项NB装备或者成功挑战了竞技场排名靠前的玩家时,会发全服广播。 76 | -------------------------------------------------------------------------------- /bin/rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuoyikang/spt_crate/3caa5adccdc9a489e9484ff94f76ca6c8d64d613/bin/rebar -------------------------------------------------------------------------------- /bin/rebar.linux: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhuoyikang/spt_crate/3caa5adccdc9a489e9484ff94f76ca6c8d64d613/bin/rebar.linux -------------------------------------------------------------------------------- /src/spt_atom.erl: -------------------------------------------------------------------------------- 1 | -module(spt_atom). 2 | -export([atom_suffix/2, atom_prefix/2, opt/3]). 3 | -export([binary_to_term/1, term_to_binary/1]). 4 | -export([string_to_term/1, term_to_string/1]). 5 | -export([mysql_escape/1, mysql_unescape/1]). 6 | 7 | atom_suffix(Table, Suffix) when is_list(Suffix)-> 8 | L = atom_to_list(Table) ++ "_" ++ Suffix, 9 | list_to_atom(L); 10 | 11 | atom_suffix(Table, Suffix) -> 12 | L = atom_to_list(Table) ++ "_" ++ atom_to_list(Suffix), 13 | list_to_atom(L). 14 | 15 | atom_prefix(Key, Prefix) when is_list(Prefix)-> 16 | L = Prefix ++ "_" ++ atom_to_list(Key), 17 | list_to_atom(L); 18 | atom_prefix(Key, Prefix) -> 19 | L = atom_to_list(Prefix) ++ "_" ++ atom_to_list(Key), 20 | list_to_atom(L). 21 | 22 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% term和binary转换. 23 | 24 | binary_to_term(<>) -> 25 | S = erlang:binary_to_list(Bin), 26 | string_to_term(S). 27 | 28 | term_to_binary(Term) -> 29 | S = term_to_string(Term), 30 | list_to_binary(S). 31 | 32 | %% term和字符串的转换. 33 | term_to_string(Term) -> 34 | R = io_lib:format("~p", [Term]), 35 | lists:flatten(R). 36 | 37 | string_to_term(String) -> 38 | TmpString1 = string:strip(String, both, $.), 39 | TmpString = string:strip(TmpString1, both, $"), 40 | NewString = TmpString ++ ".", 41 | {ok, Tokens,_EndLine} = erl_scan:string(NewString), 42 | case erl_parse:parse_exprs(Tokens) of 43 | {ok, AbsForm} -> {value, Value,_Bs} = erl_eval:exprs(AbsForm, erl_eval:new_bindings()), Value; 44 | _ -> io:format("string error ~p~n", [String]), ok 45 | end. 46 | 47 | %% 对binary进行escape操作. 48 | mysql_escape(<>) -> 49 | binary:replace(Binary, <<"\"">>, <<"\\">>, [global, {insert_replaced,1}]). 50 | 51 | mysql_unescape(<>) -> 52 | binary:replace(Binary, <<"\\">>, <<"">>, [global]). 53 | 54 | %% 参数解析. 55 | opt(Op, [{Op, Value}|_], _V) -> 56 | Value; 57 | opt(Op, [_|Options], _V) -> 58 | opt(Op, Options, _V); 59 | opt(_, [], V) -> 60 | V. 61 | -------------------------------------------------------------------------------- /src/spt_cast.erl: -------------------------------------------------------------------------------- 1 | %%% ================================================================== 2 | %%% @doc 本模块完成向指定进程组广播信息的功能 3 | %%% @end 4 | %%% ================================================================== 5 | 6 | -module(spt_cast). 7 | -behaviour(gen_server). 8 | 9 | -record(state, {key, tableid}). 10 | 11 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 12 | terminate/2, code_change/3]). 13 | 14 | -export([start_link/1, join/2, quit/2, send/2, stop/1, test/1, cast/2]). 15 | 16 | %%% ------------------------------------------------------------------- 17 | %%% 启动函数. 18 | %%% ------------------------------------------------------------------- 19 | 20 | start_link(Caster) -> 21 | Atom = list_to_atom(atom_to_list(Caster) ++ "_cast"), 22 | gen_server:start_link({local, Atom}, ?MODULE, [Atom], []). 23 | 24 | %%% -------------------------------------------------------------------- 25 | %%% 外部调用函数 26 | %%% -------------------------------------------------------------------- 27 | 28 | %% 停止函数 29 | stop(Caster) -> 30 | RegName = list_to_atom(atom_to_list(Caster) ++ "_cast"), 31 | gen_server:cast(RegName, stop). 32 | 33 | %% 加入指定广播进程组 34 | join(Caster, PID) -> 35 | RegName = list_to_atom(atom_to_list(Caster) ++ "_cast"), 36 | gen_server:call(RegName, {join, PID}). 37 | 38 | %% 推出指定广播进程组 39 | quit(Caster, PID) -> 40 | RegName = list_to_atom(atom_to_list(Caster) ++ "_cast"), 41 | gen_server:call(RegName, {quit, PID}). 42 | 43 | %% 向指定广播进程组信息 44 | send(Caster, Msg) -> 45 | RegName = list_to_atom(atom_to_list(Caster) ++ "_cast"), 46 | gen_server:call(RegName, {send, Msg}). 47 | 48 | %% 向指定广播进程组信息 49 | cast(Caster, PID) -> 50 | RegName = list_to_atom(atom_to_list(Caster) ++ "_cast"), 51 | gen_server:cast(RegName, {send, PID}). 52 | 53 | %%% --------------------------------------------------------------------- 54 | %%% 回调函数 55 | %%% --------------------------------------------------------------------- 56 | 57 | init(Atom) -> 58 | process_flag(trap_exit, true), 59 | TableId = ets:new(cast,[set, protected, {keypos, 1}]), 60 | {ok, #state{key = Atom, tableid = TableId}}. 61 | 62 | %% stop函数调用 63 | handle_cast(stop, State) -> 64 | {stop, normal, State}; 65 | 66 | %% 向已经加入的进程广播消息 67 | handle_cast({send, Msg}, State = #state{tableid = TableId}) -> 68 | cast_all(TableId, Msg), 69 | {noreply, State}; 70 | 71 | %% handle_cast其他情况 72 | handle_cast(_Info, State) -> 73 | {noreply, State}. 74 | 75 | %% 接收推出信号 76 | handle_info({'EXIT', _Pid, _Reason}, State) -> 77 | {noreply, State}. 78 | 79 | %% 进程加入广播列表 80 | handle_call({join, PID}, _From, State = #state{tableid=TableId}) -> 81 | ets:insert(TableId, {PID}), 82 | {reply, ok, State}; 83 | 84 | %% 删除已加入进程 85 | handle_call({quit, PID}, _From, State = #state{tableid=TableId}) -> 86 | ets:delete(TableId, PID), 87 | {reply, ok, State}; 88 | 89 | %% 向已经加入的进程广播消息 90 | handle_call({send, Msg}, _From, State = #state{tableid=TableId}) -> 91 | cast_all(TableId, Msg), 92 | {reply, ok, State}; 93 | 94 | %% 同步请求,直接返回 95 | handle_call(_Request, _From, State) -> 96 | {noreply, State}. 97 | 98 | %% 99 | terminate(_Reason, _State = #state{tableid = _TableId}) -> 100 | %% ets:delete(_TableId), 101 | ok. 102 | 103 | %% 104 | code_change(_OldVsn, State, _Extra) -> 105 | {ok, State}. 106 | 107 | %%% --------------------------------------------------------------------- 108 | %%% 回调函数 109 | %%% --------------------------------------------------------------------- 110 | 111 | cast_all(TableId, Msg) -> 112 | case ets:first(TableId) of 113 | '$end_of_table' -> []; 114 | Next -> cast_all(TableId, Next, Msg) 115 | end. 116 | %% 向ets中的每个元素PID,发送Msg 117 | cast_all(_TableID, '$end_of_table', _Msg) -> 118 | ok; 119 | cast_all(TableID, Key, Msg) -> 120 | Key ! Msg, 121 | cast_all(TableID, ets:next(TableID, Key), Msg). 122 | 123 | %% 接收测试信息 124 | loop() -> receive Msg -> io:format("msg:~p~n", [Msg]), loop() end. 125 | 126 | %% 测试函数 127 | test(Cast) -> 128 | {ok, _Pid} = spt_cast_sup:start_caster(Cast), 129 | Pid1 = spawn(fun loop/0), 130 | Pid2 = spawn(fun loop/0), 131 | Pid3 = spawn(fun loop/0), 132 | io:format("three msg below ~n"), 133 | spt_cast:join(Cast, Pid1), 134 | spt_cast:join(Cast, Pid2), 135 | spt_cast:join(Cast, Pid3), 136 | spt_cast:send(Cast, {msg1}), 137 | io:format("three msg below ~n"), 138 | spt_cast:send(Cast, {msg2}), 139 | io:format("two msg below ~n"), 140 | spt_cast:quit(Cast, Pid2), 141 | spt_cast:send(Cast, {msg3}), 142 | spt_cast:quit(Cast, Pid1), 143 | io:format("one msg below ~n"), 144 | spt_cast:send(Cast, {msg4}), 145 | ok. 146 | -------------------------------------------------------------------------------- /src/spt_cast_sup.erl: -------------------------------------------------------------------------------- 1 | %%% ================================================================== 2 | %%% @author:ianliao 3 | %%% @doc 管理一个进程列表,并向其中每个进程发送消息. 4 | %%% @end 5 | %%% ================================================================== 6 | 7 | -module(spt_cast_sup). 8 | -behaviour(supervisor). 9 | 10 | %% API 11 | -export([init/1, start_caster/1]). 12 | 13 | -define(MAX_RESTART, 5000000). 14 | -define(MAX_TIME, 60). 15 | 16 | %% 开启一个连接服务进程. 17 | start_caster(Cast) -> 18 | supervisor:start_child(?MODULE, [Cast]). 19 | 20 | init([]) -> 21 | {ok, 22 | {_SupFlags = {simple_one_for_one, ?MAX_RESTART, ?MAX_TIME}, 23 | [{undefined, {spt_cast, start_link, []}, transient, 2000, worker, []}] 24 | } 25 | }. 26 | -------------------------------------------------------------------------------- /src/spt_config.erl: -------------------------------------------------------------------------------- 1 | %% 用于读取配置文件动态生成erlang模块 2 | %% 假设配置文件的结构如下: 3 | %% {database, "galaxy_empire"}. 4 | %% {hostname, "localhost"}. 5 | %% {port, 3306}. 6 | %% {username, "root"}. 7 | %% {password, ""}. 8 | %% 9 | -module(spt_config). 10 | -export([gen/2]). 11 | 12 | gen(ModuleName, Path) -> 13 | M1 = spt_smerl:new(ModuleName), 14 | M2 = parse(Path, fun({K, V} , M) -> 15 | Fun = term_to_string(K) ++ "() ->" ++ term_to_string(V) ++ ".", 16 | {ok, MM} = spt_smerl:add_func(M, Fun), 17 | MM 18 | end, M1), 19 | spt_smerl:compile(M2), 20 | ok. 21 | 22 | parse(Path, Fun, State) -> 23 | case file:open(Path, read) of 24 | {ok, File} -> 25 | NewState = do_parse_line(File, Fun, State), 26 | file:close(File), 27 | NewState; 28 | {error, Reason} -> 29 | io:format("server_config Reason ~p ~n", [Reason]), 30 | State 31 | end. 32 | 33 | do_parse_line(File, Fun, State) -> 34 | case io:read(File, '') of 35 | eof -> ok, State; 36 | {error, Reason} -> io:format("server_config Reason ~p ~n", [Reason]); 37 | {ok, Term} -> 38 | NewState = Fun(Term, State), 39 | do_parse_line(File, Fun, NewState) 40 | end. 41 | 42 | term_to_string(Term) -> 43 | R = io_lib:format("~p", [Term]), 44 | lists:flatten(R). 45 | 46 | -------------------------------------------------------------------------------- /src/spt_crate.app.src: -------------------------------------------------------------------------------- 1 | {application, spt_crate, 2 | [ 3 | {description, ""}, 4 | {vsn, "1"}, 5 | {registered, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib 9 | ]}, 10 | {mod, { spt_crate_app, []}}, 11 | {env, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /src/spt_crate_app.erl: -------------------------------------------------------------------------------- 1 | -module(spt_crate_app). 2 | 3 | -behaviour(application). 4 | 5 | %% Application callbacks 6 | -export([start/2, stop/1]). 7 | 8 | %% =================================================================== 9 | %% Application callbacks 10 | %% =================================================================== 11 | 12 | start(_StartType, _StartArgs) -> 13 | spt_crate_sup:start_link(). 14 | 15 | stop(_State) -> 16 | ok. 17 | -------------------------------------------------------------------------------- /src/spt_crate_sup.erl: -------------------------------------------------------------------------------- 1 | -module(spt_crate_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | %% API 6 | -export([start_link/0]). 7 | 8 | %% Supervisor callbacks 9 | -export([init/1]). 10 | 11 | %% Helper macro for declaring children of supervisor 12 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). 13 | 14 | %% =================================================================== 15 | %% API functions 16 | %% =================================================================== 17 | 18 | start_link() -> 19 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 20 | 21 | %% =================================================================== 22 | %% Supervisor callbacks 23 | %% =================================================================== 24 | 25 | init([]) -> 26 | {ok, { {one_for_one, 5, 10}, []} }. 27 | 28 | -------------------------------------------------------------------------------- /src/spt_ets.erl: -------------------------------------------------------------------------------- 1 | -module(spt_ets). 2 | -export([safe_create/2]). 3 | 4 | %% 安全新建ets表 5 | safe_create(AtomName, Options) -> 6 | case ets:info(AtomName) of 7 | undefined -> ets:new(AtomName, Options); 8 | _Other -> AtomName 9 | end. 10 | -------------------------------------------------------------------------------- /src/spt_notify.erl: -------------------------------------------------------------------------------- 1 | %% 记录所有注册的事件及分发. 2 | %% 使用进程目的是为了托管表. 3 | -module(spt_notify). 4 | -compile([export_all]). 5 | 6 | -include_lib("eunit/include/eunit.hrl"). 7 | 8 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 9 | terminate/2, code_change/3]). 10 | 11 | -export([start_link/0]). 12 | 13 | start_link() -> 14 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 15 | 16 | stop() -> 17 | gen_server:cast(?MODULE, stop). 18 | 19 | %% 初始化表格 20 | init([]) -> 21 | ets:new(spt_notify_m, [named_table, public, bag]), 22 | {ok, spt_notify_m}. 23 | 24 | sub(Event, Fun) -> 25 | ets:insert(spt_notify_m, {Event, Fun}). 26 | 27 | ubsub(Event, Fun) -> 28 | ets:delete_object(spt_notify_m, {Event, Fun}). 29 | 30 | post(Event, Param) -> 31 | L = ets:lookup(spt_notify_m, Event), 32 | [Fun(Param) || {_, Fun} <- L]. 33 | 34 | handle_cast(stop, State) -> 35 | {stop, normal, State}; 36 | handle_cast(_, State) -> 37 | {noreply, State}. 38 | 39 | handle_call(_Msg, _From, State) -> 40 | {reply, ok, State}. 41 | 42 | handle_info(_Info, State) -> 43 | {stop, normal, State}. 44 | 45 | code_change(_OldVsn, State, _Extra) -> 46 | {ok, State}. 47 | terminate(_Reason, _State) -> 48 | ok. 49 | 50 | all_test() -> 51 | start_link(), 52 | Fun1 = fun(X) -> io:format("x1 ~p~n", [X]) end, 53 | Fun2 = fun(X) -> io:format("x2 ~p~n", [X]) end, 54 | sub(e1, Fun1), 55 | sub(e1, Fun2), 56 | post(e1, 23), 57 | ubsub(e1, Fun1), 58 | post(e1, 23), 59 | stop(), 60 | ok. 61 | -------------------------------------------------------------------------------- /src/spt_payload.erl: -------------------------------------------------------------------------------- 1 | %%% ================================================================== 2 | %%% @author:zhuoyikang 3 | %%% @doc 基础类型协议解析 4 | %%% @end 5 | %%% ================================================================== 6 | 7 | -module(spt_payload). 8 | -compile(export_all). 9 | 10 | encode_integer(Int) -> <>. 11 | decode_integer(<>) -> 12 | {Integer, Data}. 13 | 14 | 15 | encode_short(Short) -> <>. 16 | decode_short(<>) -> 17 | {Short, Data}. 18 | 19 | 20 | encode_char(Short) -> <>. 21 | decode_char(<>) -> 22 | {Short, Data}. 23 | 24 | 25 | encode_uinteger(Short) -> <>. 26 | decode_uinteger(<>) -> 27 | {Short, Data}. 28 | 29 | 30 | encode_ushort(Short) -> <>. 31 | decode_ushort(<>) -> 32 | {Short, Data}. 33 | 34 | 35 | encode_uchar(Short) -> <>. 36 | decode_uchar(<>) -> 37 | {Short, Data}. 38 | 39 | %% 解析float 40 | encode_float(Float) when is_float(Float) -> 41 | <>. 42 | decode_float(<>) -> 43 | {Float, Data}. 44 | 45 | encode_string(<>) -> 46 | L = byte_size(BinS), 47 | list_to_binary([<>, BinS]). 48 | decode_string(<>) -> 49 | {StringData, StringLeftData} = split_binary(Data,Length), 50 | {StringData, StringLeftData}. 51 | 52 | 53 | encode_boolean(Bool) when is_boolean(Bool) -> 54 | case Bool of 55 | true -> <<1:8>>; 56 | false -> <<0:8>> 57 | end. 58 | decode_boolean(<>) -> 59 | case BoolVal of 60 | 0 -> {false, Data}; 61 | _ -> {true, Data} 62 | end. 63 | -------------------------------------------------------------------------------- /src/spt_reloader.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2007 Mochi Media, Inc. 2 | %% @author Matthew Dempsky 3 | %% 4 | %% @doc Erlang module for automatically reloading modified modules 5 | %% during development. 6 | -module(spt_reloader). 7 | -author("Matthew Dempsky "). 8 | 9 | -include_lib("kernel/include/file.hrl"). 10 | 11 | -behaviour(gen_server). 12 | -export([start/0, start_link/0]). 13 | -export([stop/0]). 14 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, load_all/0]). 15 | -export([all_changed/0]). 16 | -export([is_changed/1]). 17 | -export([reload_modules/1]). 18 | -record(state, {last, tref}). 19 | 20 | %% External API 21 | 22 | %% @spec start() -> ServerRet 23 | %% @doc Start the reloader. 24 | start() -> 25 | gen_server:start({local, ?MODULE}, ?MODULE, [], []). 26 | 27 | %% @spec start_link() -> ServerRet 28 | %% @doc Start the reloader. 29 | start_link() -> 30 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 31 | 32 | %% @spec stop() -> ok 33 | %% @doc Stop the reloader. 34 | stop() -> 35 | gen_server:call(?MODULE, stop). 36 | 37 | %% gen_server callbacks 38 | 39 | %% @spec init([]) -> {ok, State} 40 | %% @doc gen_server init, opens the server in an initial state. 41 | init([]) -> 42 | {ok, TRef} = timer:send_interval(timer:seconds(1), doit), 43 | {ok, #state{last = stamp(), tref = TRef}}. 44 | 45 | %% @spec handle_call(Args, From, State) -> tuple() 46 | %% @doc gen_server callback. 47 | handle_call(stop, _From, State) -> 48 | {stop, shutdown, stopped, State}; 49 | handle_call(_Req, _From, State) -> 50 | {reply, {error, badrequest}, State}. 51 | 52 | %% @spec handle_cast(Cast, State) -> tuple() 53 | %% @doc gen_server callback. 54 | handle_cast(_Req, State) -> 55 | {noreply, State}. 56 | 57 | %% @spec handle_info(Info, State) -> tuple() 58 | %% @doc gen_server callback. 59 | handle_info(doit, State) -> 60 | Now = stamp(), 61 | _ = doit(State#state.last, Now), 62 | {noreply, State#state{last = Now}}; 63 | handle_info(_Info, State) -> 64 | {noreply, State}. 65 | 66 | %% @spec terminate(Reason, State) -> ok 67 | %% @doc gen_server termination callback. 68 | terminate(_Reason, State) -> 69 | {ok, cancel} = timer:cancel(State#state.tref), 70 | ok. 71 | 72 | 73 | %% @spec code_change(_OldVsn, State, _Extra) -> State 74 | %% @doc gen_server code_change callback (trivial). 75 | code_change(_Vsn, State, _Extra) -> 76 | {ok, State}. 77 | 78 | %% @spec reload_modules([atom()]) -> [{module, atom()} | {error, term()}] 79 | %% @doc code:purge/1 and code:load_file/1 the given list of modules in order, 80 | %% return the results of code:load_file/1. 81 | reload_modules(Modules) -> 82 | [begin code:purge(M), code:load_file(M) end || M <- Modules]. 83 | 84 | %% @spec all_changed() -> [atom()] 85 | %% @doc Return a list of beam modules that have changed. 86 | all_changed() -> 87 | [M || {M, Fn} <- code:all_loaded(), is_list(Fn), is_changed(M)]. 88 | 89 | %% @spec is_changed(atom()) -> boolean() 90 | %% @doc true if the loaded module is a beam with a vsn attribute 91 | %% and does not match the on-disk beam file, returns false otherwise. 92 | is_changed(M) -> 93 | try 94 | module_vsn(M:module_info()) =/= module_vsn(code:get_object_code(M)) 95 | catch _:_ -> 96 | false 97 | end. 98 | 99 | %% Internal API 100 | 101 | module_vsn({M, Beam, _Fn}) -> 102 | {ok, {M, Vsn}} = beam_lib:version(Beam), 103 | Vsn; 104 | module_vsn(L) when is_list(L) -> 105 | {_, Attrs} = lists:keyfind(attributes, 1, L), 106 | {_, Vsn} = lists:keyfind(vsn, 1, Attrs), 107 | Vsn. 108 | 109 | doit(From, To) -> 110 | [case file:read_file_info(Filename) of 111 | {ok, #file_info{mtime = Mtime}} when Mtime >= From, Mtime < To -> 112 | reload(Module); 113 | {ok, _} -> 114 | unmodified; 115 | {error, enoent} -> 116 | %% The Erlang compiler deletes existing .beam files if 117 | %% recompiling fails. Maybe it's worth spitting out a 118 | %% warning here, but I'd want to limit it to just once. 119 | gone; 120 | {error, Reason} -> 121 | io:format("Error reading ~s's file info: ~p~n", 122 | [Filename, Reason]), 123 | error 124 | end || {Module, Filename} <- code:all_loaded(), is_list(Filename)]. 125 | 126 | load_all() -> 127 | [reload(Module) || {Module, _Filename} <- code:all_loaded()]. 128 | 129 | reload(Module) -> 130 | io:format("Reloading ~p ...", [Module]), 131 | code:purge(Module), 132 | case code:load_file(Module) of 133 | {module, Module} -> 134 | io:format("ok.~n"), 135 | case erlang:function_exported(Module, test, 0) of 136 | true -> 137 | io:format("- Calling ~p:test() ...", [Module]), 138 | case catch Module:test() of 139 | ok -> 140 | io:format("ok.~n"), 141 | reload; 142 | Reason -> 143 | io:format("fail: ~p.~n", [Reason]), 144 | reload_but_test_failed 145 | end; 146 | false -> 147 | reload 148 | end; 149 | {error, Reason} -> 150 | io:format("fail: ~p.~n", [Reason]), 151 | error 152 | end. 153 | 154 | 155 | stamp() -> 156 | erlang:localtime(). 157 | 158 | %% 159 | %% Tests 160 | %% 161 | -ifdef(TEST). 162 | -include_lib("eunit/include/eunit.hrl"). 163 | -endif. 164 | -------------------------------------------------------------------------------- /src/spt_server.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author zhuoyikang <> 3 | %%% @copyright (C) 2016, zhuoyikang 4 | %%% @doc 5 | %%% 上一次收到消息的时间,如果超过Quit时间没有消息则退出. 6 | %%% @end 7 | %%% Created : 14 Jan 2016 by zhuoyikang <> 8 | %%%------------------------------------------------------------------- 9 | 10 | -module(spt_server). 11 | 12 | -behaviour(gen_server). 13 | 14 | %% gen_server callbacks 15 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 16 | terminate/2, code_change/3]). 17 | 18 | -define(SERVER, ?MODULE). 19 | 20 | -record(state, {model,raw_state,status,last,quit}). 21 | 22 | 23 | handle_ret({inited,SaveTime,QuitTime,RawState},State) -> 24 | timer:send_interval(SaveTime*1000, save), 25 | State1 = State#state{raw_state=RawState,status=1, quit=QuitTime}, 26 | {noreply,State1}; 27 | 28 | handle_ret({inited,QuitTime,RawState},State) -> 29 | State1 = State#state{raw_state=RawState,status=1, quit=QuitTime}, 30 | {noreply,State1}; 31 | 32 | handle_ret({ok,RawState},State) -> 33 | State1 = State#state{raw_state=RawState}, 34 | {ok,State1}; 35 | handle_ret({ok,RawState,S},State) -> 36 | State1 = State#state{raw_state=RawState}, 37 | {ok,State1,S}; 38 | handle_ret({reply,Ret,RawState},State) -> 39 | State1= State#state{raw_state=RawState}, 40 | {reply,Ret,State1}; 41 | handle_ret({reply,Ret,RawState,S},State) -> 42 | State1= State#state{raw_state=RawState}, 43 | {reply,Ret,State1,S}; 44 | handle_ret({noreply,RawState},State) -> 45 | State1 = State#state{raw_state=RawState}, 46 | {noreply,State1}; 47 | handle_ret({noreply,RawState,S},State) -> 48 | State1 = State#state{raw_state=RawState}, 49 | {noreply,State1,S}; 50 | handle_ret({stop,Reason,RawState},State) -> 51 | State1= State#state{raw_state=RawState}, 52 | {stop,Reason,State1}; 53 | handle_ret({stop,Reason,Reply,RawState},State) -> 54 | State1= State#state{raw_state=RawState}, 55 | {stop,Reason,Reply,State1}; 56 | %% 其余消息 57 | handle_ret(X,State) -> 58 | #state{model=Mod} = State, 59 | io:format("unkown ret ~p ~p", [Mod,X]), 60 | {noreply,State}. 61 | 62 | init([Mod,Args]) -> 63 | Ret=apply(Mod,init,[Args]), 64 | self() ! init, 65 | handle_ret(Ret,#state{model=Mod,last=spt_time:utc_timestamp()}). 66 | 67 | handle_call(Msg,From,State) -> 68 | #state{model=Mod,raw_state=Rs,status=Status} = State, 69 | case Status of 70 | undefined -> 71 | io:format("handle call uninited ~p ~p", [self(), Msg]), 72 | {reply,not_inited,State}; 73 | _ -> 74 | Ret=evaluate(Mod,handle_call,[Msg,From,Rs]), 75 | handle_ret(Ret,State#state{last=spt_time:utc_timestamp()}) 76 | end. 77 | 78 | handle_cast(Msg, State) -> 79 | #state{model=Mod,raw_state=Rs,status=Status} = State, 80 | case Status of 81 | undefined -> 82 | io:format("handle cast uninited ~p ~p", [self(), Msg]), 83 | {noreply,State}; 84 | _ -> 85 | Ret=evaluate(Mod,handle_cast,[Msg,Rs]), 86 | handle_ret(Ret,State#state{last=spt_time:utc_timestamp()}) 87 | end. 88 | 89 | handle_info(Info, State) -> 90 | #state{model=Mod,raw_state=Rs,status=Status,quit=Quit} = State, 91 | case {Info,Status} of 92 | {init,undefined} -> 93 | Ret = case catch evaluate(Mod,handle_info,[Info,Rs]) of X -> X end, 94 | case catch erlang:element(1,Ret) of 95 | inited -> ignore; 96 | E -> 97 | io:format("Mode init failed mod ~p reason ~p", [Mod,E]), 98 | erlang:send_after(5000, self(), init) 99 | end, 100 | handle_ret(Ret,State); 101 | {save,_} -> 102 | Last = State#state.last, 103 | Now = spt_time:utc_timestamp(), 104 | if 105 | Quit == infinity -> 106 | Ret = case catch evaluate(Mod,handle_info,[Info,Rs]) of X -> X end, 107 | handle_ret(Ret,State); 108 | (Now -Last) > Quit -> 109 | {stop,normal,State}; 110 | true -> 111 | Ret = case catch evaluate(Mod,handle_info,[Info,Rs]) of X -> X end, 112 | handle_ret(Ret,State) 113 | end; 114 | {_,undefined} -> 115 | io:format("handle info uninited ~p ~p ~p", [self(), Info, Mod]), 116 | {noreply,State}; 117 | _ -> 118 | Ret=evaluate(Mod,handle_info,[Info,Rs]), 119 | handle_ret(Ret,State#state{last=spt_time:utc_timestamp()}) 120 | end. 121 | 122 | terminate(Reason,State) -> 123 | #state{model=Mod,raw_state=Rs} = State, 124 | Ret = Mod:terminate(Reason,Rs), 125 | handle_ret(Ret,State). 126 | 127 | code_change(OldVsn, State, Extra) -> 128 | #state{model=Mod,raw_state=Rs} = State, 129 | Ret=Mod:code_change(OldVsn, Rs, Extra), 130 | handle_ret(Ret,State). 131 | 132 | time_mfa(M,F,A) -> 133 | {T, V} = timer:tc(M, F, A), 134 | %% [A1|_]=A, 135 | if 136 | T > 10000 -> 137 | %% io:format("time:~p M:~p F:~p A:~p", [T,M,F,A1]); 138 | ignore; 139 | true -> 140 | ignore 141 | end, 142 | V. 143 | 144 | 145 | %% 直接捕获异常. 146 | evaluate(M,F,A) -> 147 | catch time_mfa(M,F,A). 148 | -------------------------------------------------------------------------------- /src/spt_smerl.erl: -------------------------------------------------------------------------------- 1 | %% @author Yariv Sadan [http://yarivsblog.com] 2 | %% @copyright Yariv Sadan 2006-2007 3 | %% 4 | %% @doc Smerl: Simple Metaprogramming for Erlang 5 | %% 6 | %% Smerl is an Erlang library 7 | %% that simplifies the creation and manipulation of Erlang modules in 8 | %% runtime. 9 | %% 10 | %% You don't need to know Smerl in order to use ErlyWeb; Smerl 11 | %% is included in ErlyWeb because ErlyWeb uses it internally. 12 | %% 13 | %% Smerl uses Erlang's capabilities for hot code swapping and 14 | %% abstract syntax tree transformations to do its magic. Smerl is inspired by 15 | %% the rdbms_codegen.erl module in the RDBMS application written by 16 | %% Ulf Wiger. RDBMS is part of Jungerl ([http://jungerl.sf.net]). 17 | %% 18 | %% Here's a quick example illustrating how to use Smerl: 19 | %% ``` 20 | %% test_smerl() -> 21 | %% M1 = smerl:new(foo), 22 | %% {ok, M2} = smerl:add_func(M1, "bar() -> 1 + 1."), 23 | %% smerl:compile(M2), 24 | %% foo:bar(), % returns 2`` 25 | %% smerl:has_func(M2, bar, 0). % returns true 26 | %% ''' 27 | %% 28 | %% New functions can be expressed either as strings of Erlang code 29 | %% or as abstract forms. For more information, read the Abstract Format 30 | %% section in the ERTS User's guide 31 | %% ([http://erlang.org/doc/doc-5.5/erts-5.5/doc/html/absform.html#4]). 32 | %% 33 | %% Using the abstract format, the 3rd line of the above example 34 | %% would be written as 35 | %% ``` 36 | %% {ok,M2} = smerl:add_func(M1, {function,1,bar,0, 37 | %% [{clause,1,[],[], 38 | %% [{op,1,'+',{integer,1,1},{integer,1,1}}]}]). 39 | %% ''' 40 | %% 41 | %%

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

43 | %% 44 | 45 | %% Copyright (c) Yariv Sadan 2006-2007 46 | %% 47 | %% Permission is hereby granted, free of charge, to any person 48 | %% obtaining a copy of this software and associated documentation 49 | %% files (the "Software"), to deal in the Software without restriction, 50 | %% including without limitation the rights to use, copy, modify, merge, 51 | %% publish, distribute, sublicense, and/or sell copies of the Software, 52 | %% and to permit persons to whom the Software is furnished to do 53 | %% so, subject to the following conditions: 54 | %% 55 | %% The above copyright notice and this permission notice shall be included 56 | %% in all copies or substantial portions of the Software. 57 | %% 58 | %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 59 | %% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 60 | %% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 61 | %% IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 62 | %% CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 63 | %% TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 64 | %% SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 65 | 66 | 67 | -module(spt_smerl). 68 | -author("Yariv Sadan (yarivsblog@gmail.com, http://yarivsblog.com"). 69 | -export([new/1, 70 | for_module/1, 71 | for_module/2, 72 | for_module/3, 73 | for_file/1, 74 | for_file/2, 75 | for_file/3, 76 | get_module/1, 77 | set_module/2, 78 | get_forms/1, 79 | set_forms/2, 80 | get_exports/1, 81 | set_exports/2, 82 | get_export_all/1, 83 | set_export_all/2, 84 | remove_export/3, 85 | get_attribute/2, 86 | add_func/2, 87 | add_func/3, 88 | remove_func/3, 89 | has_func/3, 90 | get_func/3, 91 | replace_func/2, 92 | % replace_func/3, 93 | compile/1, 94 | compile/2, 95 | rename/2, 96 | curry/2, 97 | curry/4, 98 | curry/5, 99 | curry_add/3, 100 | curry_add/4, 101 | curry_add/5, 102 | curry_add/6, 103 | curry_replace/3, 104 | curry_replace/4, 105 | embed_params/2, 106 | embed_params/4, 107 | embed_params/5, 108 | embed_all/2, 109 | extend/2, 110 | extend/3, 111 | extend/4, 112 | to_src/1, 113 | to_src/2 114 | ]). 115 | 116 | -define(L(Obj), io:format("LOG ~s ~w ~p\n", [?FILE, ?LINE, Obj])). 117 | -define(S(Obj), io:format("LOG ~s ~w ~s\n", [?FILE, ?LINE, Obj])). 118 | 119 | -include_lib("kernel/include/file.hrl"). 120 | 121 | %% @type meta_mod(). A data structure holding the abstract representation 122 | %% for a module. 123 | %% @type func_form(). The abstract form for the function, as described 124 | %% in the ERTS Users' manual. 125 | 126 | %% The record type holding the abstract representation for a module. 127 | -record(meta_mod, {module, file, exports = [], forms = [], 128 | export_all = false}). 129 | 130 | %% @doc Create a new meta_mod for a module with the given name. 131 | %% 132 | %% @spec new(Module::atom()) -> meta_mod() 133 | new(ModuleName) when is_atom(ModuleName) -> 134 | #meta_mod{module = ModuleName}. 135 | 136 | 137 | %% @equiv for_module(ModuleName, []) 138 | for_module(ModuleName) -> 139 | for_module(ModuleName, []). 140 | 141 | %% @equiv for_module(ModuleName, IncludePaths, []) 142 | for_module(ModuleName, IncludePaths) -> 143 | for_module(ModuleName, IncludePaths, []). 144 | 145 | %% @doc Create a meta_mod tuple for an existing module. If ModuleName is a 146 | %% string, it is interpreted as a file name (this is the same as calling 147 | %% @{link smerl:for_file}). If ModuleName is an atom, Smerl attempts to 148 | %% find its abstract represtation either from its source file or from 149 | %% its .beam file directly (if it has been compiled with debug_info). 150 | %% If the abstract representation can't be found, this function returns 151 | %% an error. 152 | %% 153 | %% The IncludePaths parameter is used when 'ModuleName' is a file name. 154 | %% 155 | %% @spec for_module(ModuleName::atom() | string(), IncludePaths::[string()], 156 | %% Macros::[{atom(), term()}]) -> 157 | %% {ok, meta_mod()} | {error, Error} 158 | for_module(ModuleName, IncludePaths, Macros) when is_list(ModuleName) -> 159 | for_file(ModuleName, IncludePaths, Macros); 160 | for_module(ModuleName, IncludePaths, Macros) when is_atom(ModuleName) -> 161 | [_Exports, _Imports, _Attributes, 162 | {compile, [_Options, _Version, _Time, {source, Path}]}] = 163 | ModuleName:module_info(), 164 | case for_file(Path, IncludePaths, Macros) of 165 | {ok, _Mod} = Res-> 166 | Res; 167 | _Err -> 168 | case code:which(ModuleName) of 169 | Path1 when is_list(Path1) -> 170 | case get_forms(ModuleName, Path1) of 171 | {ok, Forms} -> 172 | mod_for_forms(Forms); 173 | _Other -> 174 | {error, {invalid_module, ModuleName}} 175 | end; 176 | _Err -> 177 | {error, {invalid_module, ModuleName}} 178 | end 179 | end. 180 | 181 | %% @equiv for_file(SrcFilePath, []) 182 | for_file(SrcFilePath) -> 183 | for_file(SrcFilePath, []). 184 | 185 | %% @equiv for_file(SrcFilePath, IncludePaths, []) 186 | for_file(SrcFilePath, IncludePaths) -> 187 | for_file(SrcFilePath, IncludePaths, []). 188 | 189 | %% @doc Create a meta_mod for a module from its source file. 190 | %% 191 | %% @spec for_file(SrcFilePath::string(), IncludePaths::[string()], 192 | %% Macros::[{atom(), term()}]) -> 193 | %% {ok, meta_mod()} | {error, invalid_module} 194 | for_file(SrcFilePath, IncludePaths, Macros) -> 195 | case epp:parse_file(SrcFilePath, [filename:dirname(SrcFilePath) | 196 | IncludePaths], Macros) of 197 | {ok, Forms} -> 198 | mod_for_forms(Forms); 199 | _err -> 200 | {error, {invalid_module, SrcFilePath}} 201 | end. 202 | 203 | mod_for_forms([{attribute,_,file,{FileName,_FileNum}}, 204 | {attribute, _, module, ModuleName}|Forms]) -> 205 | {Exports, OtherForms, ExportAll} = 206 | lists:foldl( 207 | fun({attribute, _, export, ExportList}, 208 | {ExportsAcc, FormsAcc, ExportAll}) -> 209 | {ExportList ++ ExportsAcc, FormsAcc, ExportAll}; 210 | ({attribute, _, compile, export_all}, 211 | {ExportsAcc, FormsAcc, _ExportAll}) -> 212 | {ExportsAcc, FormsAcc, true}; 213 | ({eof, _}, Acc) -> 214 | Acc; 215 | (Form, {ExportsAcc, FormsAcc, ExportAll}) -> 216 | {ExportsAcc, [Form | FormsAcc], ExportAll} 217 | end, {[], [], false}, Forms), 218 | {ok, #meta_mod{module = ModuleName, 219 | file = FileName, 220 | exports = Exports, 221 | forms = OtherForms, 222 | export_all = ExportAll 223 | }}; 224 | mod_for_forms(Mod) -> 225 | {error, {invalid_module, Mod}}. 226 | 227 | %% @doc Return the module name for the meta_mod. 228 | %% 229 | %% @spec(MetaMod::meta_mod()) -> atom() 230 | get_module(MetaMod) -> 231 | MetaMod#meta_mod.module. 232 | 233 | %% @doc Set the meta_mod's module name. 234 | %% 235 | %% @spec set_module(MetaMod::meta_mod(), NewName::atom()) -> 236 | %% NewMod::meta_mod() 237 | set_module(MetaMod, NewName) -> 238 | MetaMod#meta_mod{module = NewName}. 239 | 240 | %% @doc Return the list of function forms in the meta_mod. 241 | %% 242 | %% @spec get_forms(MetaMod::meta_mod()) -> [Form] 243 | get_forms(MetaMod) -> 244 | MetaMod#meta_mod.forms. 245 | 246 | set_forms(MetaMod, Forms) -> 247 | MetaMod#meta_mod{forms = Forms}. 248 | 249 | %% @doc Return the list of exports in the meta_mod. 250 | %% 251 | %% @spec get_exports(MetaMod::meta_mod()) -> 252 | %% [{FuncName::atom(), Arity::integer()}] 253 | get_exports(MetaMod) -> 254 | case MetaMod#meta_mod.export_all of 255 | false -> 256 | MetaMod#meta_mod.exports; 257 | true -> 258 | lists:foldl( 259 | fun({function, _L, Name, Arity, _Clauses}, Exports) -> 260 | [{Name, Arity} | Exports]; 261 | (_Form, Exports) -> 262 | Exports 263 | end, [], MetaMod#meta_mod.forms) 264 | end. 265 | 266 | %% @doc Set the meta_mod's export list to the new list. 267 | %% 268 | %% @spec set_exports(MetaMod::meta_mod(), 269 | %% Exports::[{FuncName::atom(), Arity::integer()}]) -> NewMod::meta_mod() 270 | set_exports(MetaMod, Exports) -> 271 | MetaMod#meta_mod{exports = Exports}. 272 | 273 | %% @doc Get the export_all value for the module. 274 | %% 275 | %% @spec get_export_all(MetaMod::meta_mod) -> true | false 276 | get_export_all(MetaMod) -> 277 | MetaMod#meta_mod.export_all. 278 | 279 | %% @doc Set the export_all value for the module. 280 | %% 281 | %% @spec set_export_all(MetaMod::meta_mod(), Val::true | false) -> 282 | %% NewMetaMod::meta_mod() 283 | set_export_all(MetaMod, Val) -> 284 | MetaMod#meta_mod{export_all = Val}. 285 | 286 | %% @doc Remove the export from the list of exports in the meta_mod. 287 | %% 288 | %% @spec remove_export(MetaMod::meta_mod(), FuncName::atom(), 289 | %% Arity::integer()) -> NewMod::meta_mod() 290 | remove_export(MetaMod, FuncName, Arity) -> 291 | MetaMod#meta_mod{exports = 292 | lists:delete({FuncName, Arity}, 293 | MetaMod#meta_mod.exports)}. 294 | 295 | %% @doc Get the value a the module's attribute. 296 | %% 297 | %% @spec get_attribute(MetaMod::meta_mod(), AttName::atom()) -> 298 | %% {ok, Val} | error 299 | get_attribute(MetaMod, AttName) -> 300 | case lists:keysearch(AttName, 3, get_forms(MetaMod)) of 301 | {value, {attribute,_,_,Val}} -> 302 | {ok, Val}; 303 | _ -> error 304 | end. 305 | 306 | 307 | %% Get the abstract representation, if available, for the module. 308 | %% 309 | %% Strategy: 310 | %% 1) Try to get the abstract code from the module if it's compiled 311 | %% with debug_info. 312 | %% 2) Look for the source file in the beam file's directory. 313 | %% 3) If the file's directory ends with 'ebin', then search in 314 | %% [beamdir]/../src 315 | get_forms(Module, Path) -> 316 | case beam_lib:chunks(Path, [abstract_code]) of 317 | {ok, {_, [{abstract_code, {raw_abstract_v1, Forms}}]}} -> 318 | {ok, Forms}; 319 | _Err -> 320 | case filename:find_src(Module, [{"ebin", "src"}]) of 321 | {error, _} = Err -> 322 | get_forms_from_binary(Module, Err); 323 | {SrcPath, _} -> 324 | Filename = SrcPath ++ ".erl", 325 | epp:parse_file(Filename, [filename:dirname(Filename)], []) 326 | end 327 | end. 328 | 329 | get_dirs_in_dir(Dir) -> 330 | case file:list_dir(Dir) of 331 | {error, _} -> 332 | undefined; 333 | {ok, Listing} -> 334 | lists:foldl( 335 | fun (Name, Acc) -> 336 | Path = Dir ++ "/" ++ Name, 337 | case file:read_file_info(Path) of 338 | {ok, #file_info{type=directory}} -> [Path | Acc]; 339 | _ -> Acc 340 | end 341 | end, [], Listing) 342 | end. 343 | 344 | %% @doc Try to infer module source files from the beam code path. 345 | get_forms_from_binary(Module, OrigErr) -> 346 | Ret = 347 | case code:where_is_file(atom_to_list(Module) ++ ".beam") of 348 | non_existing -> 349 | OrigErr; 350 | Filename -> 351 | %% We could automatically obtain a list of all dirs under this dir, but we just do 352 | %% a hack for now. 353 | Basedir = filename:dirname(Filename), 354 | Lastdir = filename:basename(Basedir), 355 | if Lastdir == "ebin" -> 356 | Rootdir = filename:dirname(Basedir), 357 | DirList = [Rootdir ++ "/src"], 358 | get_forms_from_file_list(Module, Rootdir, 359 | DirList ++ get_dirs_in_dir(Rootdir ++ "/src")); 360 | true -> 361 | DirList = [Basedir], 362 | get_forms_from_file_list(Module, Basedir, DirList) 363 | end 364 | end, 365 | if Ret == [] -> OrigErr; 366 | true -> Ret 367 | end. 368 | get_forms_from_file_list(_Module, _Basedir, []) -> 369 | []; 370 | get_forms_from_file_list(Module, Basedir, [H|T]) -> 371 | Filename = H ++ "/" ++ atom_to_list(Module) ++ ".erl", 372 | case file:read_file_info(Filename) of 373 | {ok, #file_info{type=regular}} -> 374 | epp:parse_file(Filename, [filename:dirname(Filename)], []); 375 | _ -> 376 | get_forms_from_file_list(Module, Basedir, T) 377 | end. 378 | 379 | %% @doc Add a new function to the meta_mod and return the resulting meta_mod. 380 | %% This function calls add_func(MetaMod, Form, true). 381 | %% 382 | %% @spec add_func(MetaMod::meta_mod(), Form::func_form() | string()) -> 383 | %% {ok, NewMod::meta_mod()} | {error, parse_error} 384 | add_func(MetaMod, Form) -> 385 | add_func(MetaMod, Form, true). 386 | 387 | %% @doc Add a new function to the meta_mod and return the new MetaMod 388 | %% record. Export is a boolean variable indicating if the function should 389 | %% be added to the module's exports. 390 | %% 391 | %% @spec add_func(MetaMod::meta_mod(), Func::func_form() | string(), 392 | %% Export::boolean()) -> 393 | %% {ok, NewMod::meta_mod()} | {error, parse_error} 394 | add_func(MetaMod, Func, Export) when is_list(Func) -> 395 | case parse_func_string(Func) of 396 | {ok, Form} -> 397 | add_func(MetaMod, Form, Export); 398 | Err -> 399 | Err 400 | end; 401 | add_func(MetaMod, {function, _Line, FuncName, Arity, _Clauses} = Form, 402 | true) -> 403 | Foo = {ok, MetaMod#meta_mod{ 404 | exports = [{FuncName, Arity} | MetaMod#meta_mod.exports], 405 | forms = [Form | MetaMod#meta_mod.forms] 406 | }}, 407 | Foo; 408 | add_func(MetaMod, {function, _Line, _FuncName, _Arity, _Clauses} = Form, 409 | false) -> 410 | {ok, MetaMod#meta_mod{forms = [Form | MetaMod#meta_mod.forms]}}; 411 | 412 | %%add_func(MetaMod, Name, Fun) when is_function(Fun) -> 413 | %% add_func(MetaMod, Name, Fun, true); 414 | 415 | add_func(_, _, _) -> 416 | {error, parse_error}. 417 | 418 | %% add_func(MetaMod, Name, Fun, Export) when is_function(Fun) -> 419 | %% case form_for_fun(Name, Fun) of 420 | %% {ok, Form} -> 421 | %% add_func(MetaMod, Form, Export); 422 | %% Err -> 423 | %% Err 424 | %% end. 425 | 426 | %% form_for_fun(Name, Fun) -> 427 | %% Line = 999, 428 | %% Info = erlang:fun_info(Fun), 429 | %% case Info of 430 | %% [{module, _ModName}, _FuncName, _Arity, _Env, {type, external}] -> 431 | %% {error, cant_add_external_funcs}; 432 | %% [_Pid, _Module, _NewIdx, _NewUniq, _Index, _Uniq, _Name, 433 | %% {arity, Arity}, 434 | %% {env, [Vars, _Unknown1, _Unknown2, Clauses]}, 435 | %% {type, local}] -> 436 | %% EnvVars = lists:map( 437 | %% fun({VarName, Val}) -> 438 | %% {match,Line,{var,Line,VarName}, 439 | %% erl_parse:abstract(Val)} 440 | %% end, Vars), 441 | %% NewClauses = lists:map( 442 | %% fun({clause, Line1, Params, Guards, Exprs}) -> 443 | %% {clause, Line1, Params, Guards, 444 | %% EnvVars ++ Exprs} 445 | %% end, Clauses), 446 | %% {ok, {function, Line, Name, Arity, NewClauses}}; 447 | %% _Other -> 448 | %% {error, bad_fun} 449 | %% end. 450 | 451 | 452 | parse_func_string(Func) -> 453 | case erl_scan:string(Func) of 454 | {ok, Toks, _} -> 455 | case erl_parse:parse_form(Toks) of 456 | {ok, _Form} = Res -> 457 | Res; 458 | _Err -> 459 | {error, parse_error} 460 | end; 461 | _Err -> 462 | {error, parse_error} 463 | end. 464 | 465 | %% @doc Try to remove the function from the meta_mod. 466 | %% If the function exists, the new meta_mod is returned. Otherwise, 467 | %% original meta_mod is returned. 468 | %% 469 | %% @spec remove_func(MetaMod::meta_mod(), FuncName::string(), Arity::integer()) 470 | %% -> NewMod::meta_mod() 471 | %% 472 | remove_func(MetaMod, FuncName, Arity) -> 473 | MetaMod#meta_mod{forms = 474 | lists:filter( 475 | fun({function, _Line, FuncName1, Arity1, _Clauses}) 476 | when FuncName1 =:= FuncName, Arity =:= Arity1-> 477 | false; 478 | (_) -> 479 | true 480 | end, MetaMod#meta_mod.forms), 481 | exports = 482 | lists:filter( 483 | fun({FuncName1, Arity1}) 484 | when FuncName1 =:= FuncName, 485 | Arity1 =:= Arity -> 486 | false; 487 | (_) -> 488 | true 489 | end, MetaMod#meta_mod.exports) 490 | }. 491 | 492 | %% @doc Check whether the meta_mod has a function with the given name 493 | %% and arity. 494 | %% @spec has_func(MetaMod::meta_mod(), FuncName::atom(), Arity::integer()) -> 495 | %% bool() 496 | has_func(MetaMod, FuncName, Arity) -> 497 | lists:any(fun({function, _Line, FuncName1, Arity1, _Clauses}) 498 | when FuncName1 == FuncName, Arity1 == Arity -> 499 | true; 500 | (_) -> 501 | false 502 | end, MetaMod#meta_mod.forms). 503 | 504 | 505 | %% @doc Get the form for the function with the specified arity in the 506 | %% meta_mod. 507 | %% 508 | %% @spec get_func(MetaMod::meta_mod() | atom(), 509 | %% FuncName::atom(), Arity::integer()) -> 510 | %% {ok, func_form()} | {error, Err} 511 | get_func(Module, FuncName, Arity) when is_atom(Module) -> 512 | case smerl:for_module(Module) of 513 | {ok, C1} -> 514 | get_func(C1, FuncName, Arity); 515 | Err -> 516 | Err 517 | end; 518 | get_func(MetaMod, FuncName, Arity) -> 519 | get_func2(MetaMod#meta_mod.forms, FuncName, Arity). 520 | 521 | get_func2([], FuncName, Arity) -> 522 | {error, {function_not_found, {FuncName, Arity}}}; 523 | get_func2([{function, _Line, FuncName, Arity, _Clauses} = Form | _Rest], 524 | FuncName, Arity) -> 525 | {ok, Form}; 526 | get_func2([_Form|Rest], FuncName, Arity) -> 527 | get_func2(Rest, FuncName, Arity). 528 | 529 | 530 | 531 | %% @doc 532 | %% Replace an existing function with the new one. If the function doesn't exist 533 | %% the new function is added to the meta_mod. 534 | %% This is tantamount to calling smerl:remove_func followed by smerl:add_func. 535 | %% 536 | %% @spec replace_func(MetaMod::meta_mod(), Function::string() | func_form()) -> 537 | %% {ok, NewMod::meta_mod()} | {error, Error} 538 | replace_func(MetaMod, Function) when is_list(Function) -> 539 | case parse_func_string(Function) of 540 | {ok, Form} -> 541 | replace_func(MetaMod, Form); 542 | Err -> 543 | Err 544 | end; 545 | replace_func(MetaMod, {function, _Line, FuncName, Arity, _Clauses} = Form) -> 546 | Mod1 = remove_func(MetaMod, FuncName, Arity), 547 | add_func(Mod1, Form); 548 | replace_func(_MetaMod, _) -> 549 | {error, parse_error}. 550 | 551 | %% %% @doc Simliar to replace_func/2, but accepts a function 552 | %% %% name + fun expression. 553 | %% %% 554 | %% %% @spec replace_func(MetaMod::meta_mod(), Name::atom(), Fun::function()) -> 555 | %% %% {ok, NewMod::meta_mod()} | {error, Error} 556 | %% replace_func(MetaMod, Name, Fun) when is_function(Fun) -> 557 | %% case form_for_fun(Name, Fun) of 558 | %% {ok, Form} -> 559 | %% replace_func(MetaMod, Form); 560 | %% Err -> 561 | %% Err 562 | %% end. 563 | 564 | %% @doc Compile the module represented by the meta_mod and load the 565 | %% resulting BEAM into the emulator. This function calls 566 | %% compile(MetaMod, [report_errors, report_warnings]). 567 | %% 568 | %% @spec compile(MetaMod::meta_mod()) -> ok | {error, Error} 569 | compile(MetaMod) -> 570 | compile(MetaMod, undefined). 571 | 572 | %% @doc Compile the module represented by the meta_mod and load the 573 | %% resulting BEAM into the emulator. 'Options' is a list of options as 574 | %% described in the 'compile' module in the Erlang documentation. 575 | %% 576 | %% If the 'outdir' option is provided, 577 | %% the .beam file is written to the destination directory. 578 | %% 579 | %% @spec compile(MetaMod::meta_mod(), Options::[term()]) -> ok | {error, Error} 580 | compile(MetaMod, undefined) -> 581 | compile(MetaMod, [report_errors, report_warnings, 582 | return_errors]); 583 | 584 | compile(MetaMod, Options) -> 585 | Forms = [{attribute, 2, module, MetaMod#meta_mod.module}, 586 | {attribute, 3, export, get_exports(MetaMod)}], 587 | FileName = 588 | case MetaMod#meta_mod.file of 589 | undefined -> atom_to_list(get_module(MetaMod)); 590 | Val -> Val 591 | end, 592 | 593 | Forms1 = [{attribute, 1, file, {FileName, 1}} | Forms], 594 | Forms2 = Forms1 ++ lists:reverse(MetaMod#meta_mod.forms), 595 | 596 | case compile:forms(Forms2, Options) of 597 | {ok, Module, Bin} -> 598 | Res = 599 | case lists:keysearch(outdir, 1, Options) of 600 | {value, {outdir, OutDir}} -> 601 | file:write_file( 602 | OutDir ++ 603 | ['/' | atom_to_list(MetaMod#meta_mod.module)] ++ 604 | ".beam", Bin); 605 | false -> ok 606 | end, 607 | case Res of 608 | ok -> 609 | code:purge(Module), 610 | case code:load_binary( 611 | Module, 612 | atom_to_list(Module) ++ ".erl", Bin) of 613 | {module, _Module} -> 614 | ok; 615 | Err -> 616 | Err 617 | end; 618 | Err -> 619 | Err 620 | end; 621 | Err -> 622 | Err 623 | end. 624 | 625 | %% @doc Change the name of the function represented by the form. 626 | %% 627 | %% @spec rename(Form::func_form(), NewName::atom()) -> 628 | %% {ok, NewForm::func_form()} | {error, Err} 629 | rename({function, Line, _Name, Arity, Clauses}, NewName) -> 630 | {function, Line, NewName, Arity, Clauses}. 631 | 632 | 633 | %% @doc Get the curried form for the function and parameter(s). Currying 634 | %% involves replacing one or more of the function's leading parameters 635 | %% with predefined values. 636 | %% 637 | %% @spec curry(Form::func_form(), Param::term() | [term()]) -> 638 | %% {ok, NewForm::func_form()} | {error, Err} 639 | curry(Form, Param) when not is_list(Param) -> 640 | curry(Form, [Param]); 641 | curry({function, _Line, _Name, Arity, _Clauses}, Params) 642 | when length(Params) > Arity -> 643 | {error, too_many_params}; 644 | curry({function, Line, Name, Arity, Clauses}, NewParams) -> 645 | NewClauses = 646 | lists:foldl( 647 | fun(Clause, Clauses1) -> 648 | [curry_clause(Clause, NewParams) | Clauses1] 649 | end, [], Clauses), 650 | {ok, {function, Line, Name, Arity-length(NewParams), NewClauses}}. 651 | 652 | curry_clause({clause, L1, ExistingParams, Guards, _Exprs} = Clause, 653 | NewParams) -> 654 | {FirstParams, LastParams} = 655 | lists:split(length(NewParams), ExistingParams), 656 | %% Matches = 657 | %% lists:foldl( 658 | %% fun({Var, NewVal}, Acc) -> 659 | %% [{match, 1, Var, erl_parse:abstract(NewVal)} | Acc] 660 | %% end, [], lists:zip(FirstParams, NewParams)), 661 | %% {clause, L1, LastParams, Guards, Matches ++ Exprs}. 662 | 663 | Vals = 664 | lists:foldl( 665 | fun({{var,_,Name}, NewVal}, Acc) -> 666 | [{Name, erl_parse:abstract(NewVal)} | Acc]; 667 | (_, Acc) -> 668 | Acc 669 | end, [], lists:zip(FirstParams, NewParams)), 670 | 671 | NewExprs = replace_vars(Clause, Vals), 672 | 673 | {clause, L1, LastParams, Guards, NewExprs}. 674 | 675 | replace_vars(Clause, Vals) -> 676 | Tree = 677 | erl_syntax_lib:map( 678 | fun({var,_L2,Name} = Expr) -> 679 | case proplists:lookup(Name, Vals) of 680 | none -> 681 | Expr; 682 | {_, Val} -> 683 | Val 684 | end; 685 | (Expr) -> 686 | Expr 687 | end, Clause), 688 | {clause, _, _, _, NewExprs} = erl_syntax:revert(Tree), 689 | NewExprs. 690 | 691 | 692 | %% @doc Curry the function from the module with the given param(s) 693 | %% 694 | %% @spec curry(ModName::atom(), Name::atom(), Arity::integer(), 695 | %% Params::term() | [term()]) -> 696 | %% {ok, NewForm} | {error, Err} 697 | curry(ModName, Name, Arity, Params) when is_atom(ModName) -> 698 | case for_module(ModName) of 699 | {ok, MetaMod} -> 700 | curry(MetaMod, Name, Arity, Params); 701 | Err -> 702 | Err 703 | end; 704 | 705 | %% @doc Curry the function from the meta_mod with 706 | %% the given param(s) 707 | %% 708 | %% @spec curry(MetaMod::meta_mod(), Name::atom(), arity::integer(), 709 | %% Params::term() | [terms()]) -> 710 | %% {ok, NewForm} | {error, Err} 711 | curry(MetaMod, Name, Arity, Params) -> 712 | case get_func(MetaMod, Name, Arity) of 713 | {ok, Form} -> 714 | curry(Form, Params); 715 | Err -> 716 | Err 717 | end. 718 | 719 | 720 | 721 | %% @doc Curry the function from the module or meta_mod 722 | %% with the param(s), and return its renamed form. 723 | %% 724 | %% @spec curry(Module::atom() | meta_mod(), Name::atom(), Arity::integer(), 725 | %% Params::term() | [terms()], NewName::atom()) -> 726 | %% {ok, NewForm} | {error, Err} 727 | curry(Module, Name, Arity, Params, NewName) -> 728 | case curry(Module, Name, Arity, Params) of 729 | {ok, NewForm} -> 730 | {ok, rename(NewForm, NewName)}; 731 | Err -> 732 | Err 733 | end. 734 | 735 | 736 | %% @doc Add the curried form of the function in the meta_mod 737 | %% with its curried form. 738 | %% 739 | %% @spec curry_add(MetaMod::meta_mod(), Form::func_form(), 740 | %% Params::term() | [term()]) -> 741 | %% {ok, NewMetaMod::meta_mod()} | {error, Err} 742 | curry_add(MetaMod, {function, _Line, Name, Arity, _Clauses}, Params) -> 743 | curry_add(MetaMod, Name, Arity, Params). 744 | 745 | %% @doc Add the curried form of the function 746 | %% in the meta_mod with its curried form. 747 | %% 748 | %% @spec curry_add(MetaMod::meta_mod(), Name::atom(), Arity::integer(), 749 | %% Params::term() | [term()]) -> 750 | %% {ok, NewMetaMod::meta_mod()} | {error, Err} 751 | curry_add(MetaMod, Name, Arity, Params) -> 752 | curry_change(MetaMod, Name, Arity, Params, false). 753 | 754 | %% @doc Curry the function form from the meta_mod, then add it 755 | %% to the other meta_mod with a new name. 756 | %% 757 | %% @spec curry_add(MetaMod::meta_mod(), Name::atom(), Arity::integer(), 758 | %% Params::[term()], NewName::atom()) -> {ok, NewMod::meta_mod()} | 759 | %% {error, Err} 760 | curry_add(MetaMod, Name, Arity, Params, NewName) -> 761 | curry_add(MetaMod, MetaMod, Name, Arity, Params, NewName). 762 | 763 | %% @doc Curry the function in the module, rename the curried form, and 764 | %% add it to the meta_mod. 765 | %% 766 | %% @spec curry_add(MetaMod::meta_mod(), Module::atom() | meta_mod(), 767 | %% Name::atom(), Arity::integer(), Params::term() | [term()], 768 | %% NewName::atom()) -> 769 | %% {ok, NewMod::meta_mod()} | {error, Error} 770 | curry_add(MetaMod, Module, Name, Arity, Params, NewName) -> 771 | case curry(Module, Name, Arity, Params, NewName) of 772 | {ok, Form} -> 773 | add_func(MetaMod, Form); 774 | Err -> 775 | Err 776 | end. 777 | 778 | curry_change(MetaMod, Name, Arity, Params, Remove) -> 779 | case get_func(MetaMod, Name, Arity) of 780 | {ok, OldForm} -> 781 | case curry(OldForm, Params) of 782 | {ok, NewForm} -> 783 | MetaMod1 = 784 | case Remove of 785 | true -> 786 | remove_func(MetaMod, Name, Arity); 787 | false -> 788 | MetaMod 789 | end, 790 | add_func(MetaMod1, NewForm); 791 | Err -> 792 | Err 793 | end; 794 | Err -> 795 | Err 796 | end. 797 | 798 | %% @doc Replace the function in the meta_mod with 799 | %% its curried form. 800 | %% 801 | %% @spec curry_replace(MetaMod::meta_mod(), Form::func_form(), 802 | %% Params::term() | [term()]) -> 803 | %% {ok, NewMetaMod::meta_mod()} | {error, Err} 804 | curry_replace(MetaMod, {function, _Line, Name, Arity, _Clauses}, Params) -> 805 | curry_replace(MetaMod, Name, Arity, Params). 806 | 807 | 808 | %% @doc Replace the function in the meta_mod with 809 | %% its curried form. 810 | %% 811 | %% @spec curry_replace(MetaMod::meta_mod(), Name::atom(), 812 | %% Arity::integer(), Params::term() | list()) -> 813 | %% {ok, NewMetaMod::meta_mod()} | {error, Err} 814 | curry_replace(MetaMod, Name, Arity, Params) -> 815 | curry_change(MetaMod, Name, Arity, Params, true). 816 | 817 | 818 | %% @doc This function takes a function form and list of name/value pairs, 819 | %% and replaces all the function's parameters that whose names match an 820 | %% element from the list with the predefined value. 821 | %% 822 | %% @spec embed_params(Func::func_form(), 823 | %% Vals::[{Name::atom(), Value::term()}]) -> NewForm::func_form() 824 | embed_params({function, L, Name, Arity, Clauses}, Vals) -> 825 | NewClauses = 826 | lists:map( 827 | fun({clause, L1, Params, Guards, _Exprs} = Clause) -> 828 | {EmbeddedVals, OtherParams} = 829 | lists:foldr( 830 | fun({var,_, VarName} = Param, {Embedded, Rest}) -> 831 | case proplists:lookup(VarName, Vals) of 832 | none -> 833 | {Embedded, [Param | Rest]}; 834 | {_, Val} -> 835 | {[{VarName, erl_parse:abstract(Val)} | 836 | Embedded], Rest} 837 | end; 838 | (Param, {Embedded, Rest}) -> 839 | {Embedded, [Param | Rest]} 840 | end, {[], []}, Params), 841 | NewExprs = replace_vars(Clause, EmbeddedVals), 842 | {clause, L1, OtherParams, Guards, NewExprs} 843 | 844 | 845 | %% {Params1, Matches1, _RemainingVals} = 846 | %% lists:foldl( 847 | %% fun({var, _L2, ParamName} = Param, 848 | %% {Params2, Matches2, Vals1}) -> 849 | %% case lists:keysearch(ParamName, 1, Vals1) of 850 | %% {value, {_Name, Val} = Elem} -> 851 | %% Match = {match, L1, Param, 852 | %% erl_parse:abstract(Val)}, 853 | %% {Params2, [Match | Matches2], 854 | %% lists:delete(Elem, Vals1)}; 855 | %% false -> 856 | %% {[Param | Params2], Matches2, Vals1} 857 | %% end; 858 | %% (Param, {Params2, Matches2, Vals1}) -> 859 | %% {[Param | Params2], Matches2, Vals1} 860 | %% end, {[], [], Vals}, Params), 861 | %% [{clause, L1, lists:reverse(Params1), Guards, 862 | %% lists:reverse(Matches1) ++ Exprs} | Clauses1] 863 | end, Clauses), 864 | NewArity = 865 | case NewClauses of 866 | [{clause, _L2, Params, _Guards, _Exprs}|_] -> 867 | length(Params); 868 | _ -> 869 | Arity 870 | end, 871 | {function, L, Name, NewArity, NewClauses}. 872 | 873 | %% @doc Apply {@link embed_params/2} to a function from the meta_mod and 874 | %% add the resulting function to the meta_mod, and return the resulting 875 | %% meta_mod. 876 | %% 877 | %% @spec embed_params(MetaMod::meta_mod(), Name::atom(), Arity::integer(), 878 | %% Values::proplist()) -> {ok, NewMetaMod::meta_mod()} | {error, Err} 879 | embed_params(MetaMod, Name, Arity, Values) -> 880 | embed_params(MetaMod, Name, Arity, Values, Name). 881 | 882 | %% @doc Apply embed_params/2 to the function from the meta_mod and 883 | %% add the resulting function to the meta_mod after renaming the function. 884 | %% 885 | %% @spec embed_params(MetaMod::meta_mod(), Name::atom(), Arity::integer(), 886 | %% Values::proplist(), NewName::atom()) -> 887 | %% {ok, NewMetaMod::meta_mod()} | {error, Err} 888 | embed_params(MetaMod, Name, Arity, Values, NewName) -> 889 | case get_func(MetaMod, Name, Arity) of 890 | {ok, Form} -> 891 | NewForm = embed_params(Form, Values), 892 | add_func(MetaMod, rename(NewForm, NewName)); 893 | Err -> 894 | Err 895 | end. 896 | 897 | 898 | 899 | 900 | %% @doc Apply the embed_params function with the list of {Name, Value} 901 | %% pairs to all forms in the meta_mod. Exports 902 | %% for functions whose arities change due to the embedding are preserved. 903 | %% 904 | %% @spec embed_all(MetaMod::meta_mod(), Vals::[{Name::atom(), 905 | %% Value::term()}]) -> NewMetaMod::meta_mod() 906 | embed_all(MetaMod, Vals) -> 907 | Forms = get_forms(MetaMod), 908 | Exports = get_exports(MetaMod), 909 | {NewForms, Exports3, NewExports} = 910 | lists:foldl( 911 | fun({function, _L, Name, Arity, _Clauses} = Form, 912 | {Forms1, Exports1, NewExports1}) -> 913 | {function, _, _, NewArity, _} = NewForm = 914 | embed_params(Form, Vals), 915 | Exports2 = lists:delete({Name, Arity}, Exports1), 916 | NewExports2 = 917 | case length(Exports2) == length(Exports1) of 918 | true -> 919 | NewExports1; 920 | false -> 921 | [{Name, NewArity} | NewExports1] 922 | end, 923 | {[NewForm | Forms1], Exports2, NewExports2}; 924 | (Form, {Forms1, Exports1, NewExports1}) -> 925 | {[Form | Forms1], Exports1, NewExports1} 926 | end, {[], Exports, []}, Forms), 927 | #meta_mod{module = get_module(MetaMod), 928 | exports = Exports3 ++ NewExports, 929 | forms = lists:reverse(NewForms), 930 | export_all = get_export_all(MetaMod)}. 931 | 932 | %% @doc extend/2 933 | %% Add all the parent module's functions that are missing from the child 934 | %% module to the child module. The new functions in the child module are 935 | %% shallow: they have the name and arity as their corresponding functions in 936 | %% the parent meta_mod, but instead of implementing their logic they call 937 | %% the parent module's functions. 938 | %% 939 | %% @spec extend(Parent::atom() | meta_mod(), Child::atom() | meta_mod()) -> 940 | %% NewChildMod::meta_mod() 941 | extend(Parent, Child) -> 942 | extend(Parent, Child, 0). 943 | 944 | %% @doc Similar to extend/2, with the addition of the 'ArityDiff' parameter, 945 | %% which indicates the difference 946 | %% in arities Smerl should use when figuring out which functions to 947 | %% generate based on the modules' exports. This is sometimes 948 | %% useful when calling extend() followed by embed_all(). 949 | %% 950 | %% @spec extend(Parent::atom() | meta_mod(), Child::atom() | meta_mod(), 951 | %% ArityDiff::integer()) -> 952 | %% NewChildMod::meta_mod() 953 | extend(Parent, Child, ArityDiff) -> 954 | extend(Parent, Child, ArityDiff, []). 955 | 956 | extend(Parent, Child, ArityDiff, Options) -> 957 | {{ParentName, ParentExports, ParentMod}, ChildMod} = 958 | get_extend_data(Parent, Child), 959 | ChildExports = get_exports(ChildMod), 960 | ChildExports1 = [{ExportName, ExportArity + ArityDiff} || 961 | {ExportName, ExportArity} <- 962 | ChildExports], 963 | ExportsDiff = ParentExports -- ChildExports1, 964 | NewChild = 965 | lists:foldl( 966 | fun({FuncName, Arity}, ChildMod1) -> 967 | Func = 968 | case lists:member(copy, Options) of 969 | true -> 970 | {ok, ParentFunc} = 971 | smerl:get_func(ParentMod, FuncName, Arity), 972 | ParentFunc; 973 | _ -> 974 | Params = get_params( 975 | ParentMod, FuncName, Arity), 976 | Clause1 = 977 | {clause,1,Params,[], 978 | [{call,1, 979 | {remote,1,{atom,1,ParentName}, 980 | {atom,1,FuncName}}, 981 | Params}]}, 982 | {function,1,FuncName,Arity, [Clause1]} 983 | end, 984 | {ok, ChildMod2} = add_func(ChildMod1, Func), 985 | ChildMod2 986 | end, ChildMod, ExportsDiff), 987 | NewChild. 988 | 989 | get_extend_data(Parent, Child) when is_atom(Parent) -> 990 | [{exports, Exports} |_] = Parent:module_info(), 991 | Exports1 = Exports -- [{module_info, 0}], 992 | Exports2 = Exports1 -- [{module_info, 1}], 993 | ParentMod = case smerl:for_module(Parent) of 994 | {ok, M} -> M; 995 | {error, _} -> undefined 996 | end, 997 | get_extend_data({Parent, Exports2, ParentMod}, Child); 998 | get_extend_data(Parent, Child) when is_record(Parent, meta_mod) -> 999 | get_extend_data({get_module(Parent), 1000 | get_exports(Parent), 1001 | Parent}, Child); 1002 | get_extend_data(Parent, Child) when is_list(Parent) -> 1003 | case for_file(Parent) of 1004 | {ok, M1} -> 1005 | get_extend_data(M1, Child); 1006 | Err -> 1007 | Err 1008 | end; 1009 | get_extend_data({_,_,_} = ParentData, Child) when is_atom(Child); 1010 | is_list(Child) -> 1011 | case for_module(Child) of 1012 | {ok, MetaMod} -> 1013 | {ParentData, MetaMod}; 1014 | Err -> 1015 | Err 1016 | end; 1017 | get_extend_data(ParentData, Child) when is_record(Child, meta_mod) -> 1018 | {ParentData, Child}. 1019 | 1020 | get_params(_, _, 0) -> []; 1021 | get_params(undefined, _FuncName, Arity) -> 1022 | [{var,1,list_to_atom("P" ++ integer_to_list(Num))} 1023 | || Num <- lists:seq(1, Arity)]; 1024 | get_params(ParentMod, FuncName, Arity) -> 1025 | {ok, {function, _L, _Name, _Arity, 1026 | [{clause,_,Params,_Guards,_Exprs} | _]}} = 1027 | get_func(ParentMod, FuncName, Arity), 1028 | Params. 1029 | 1030 | 1031 | %% @doc Return the pretty-printed source code for the module. 1032 | %% 1033 | %% @spec to_src(MetaMod::meta_mod()) -> string() 1034 | to_src(MetaMod) -> 1035 | ExportsForm = 1036 | {attribute,1,export,get_exports(MetaMod)}, 1037 | AllForms = [{attribute,1,module,get_module(MetaMod)}, ExportsForm | 1038 | get_forms(MetaMod)], 1039 | erl_prettypr:format(erl_syntax:form_list(AllForms)). 1040 | 1041 | %% @doc Write the pretty printed source code for the module 1042 | %% to the file with the given file name. 1043 | %% 1044 | %% @spec to_src(MetaMod::meta_mod(), FileName::string()) -> 1045 | %% ok | {error, Error} 1046 | to_src(MetaMod, FileName) -> 1047 | Src = to_src(MetaMod), 1048 | file:write_file(FileName, list_to_binary(Src)). 1049 | -------------------------------------------------------------------------------- /src/spt_time.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author zhuoyikang <> 3 | %%% @copyright (C) 2016, zhuoyikang 4 | %%% @doc 5 | %%% 封装一些时间相关函数. 6 | %%% @end 7 | %%% Created : 14 Jan 2016 by zhuoyikang <> 8 | %%%------------------------------------------------------------------- 9 | 10 | 11 | -module(spt_time). 12 | 13 | -export([utc_timestamp/0]). 14 | 15 | 16 | %% -------------------------------------- 17 | %% @doc 服务器Unix时间戳 18 | %% -------------------------------------- 19 | 20 | utc_timestamp() -> 21 | {MegaSecs, Secs, _} = os:timestamp(), 22 | MegaSecs * 1000000 + Secs. 23 | --------------------------------------------------------------------------------