-module(rest). %%-author('Dmitry Bushmelev'). -export([behaviour_info/1, parse_transform/2, generate_to_json/3, binarize/1, generate_from_json/3, from_json/2, to_json/1, to_binary/1, parse/1, atomize/1]). behaviour_info(callbacks) -> [{exists, 1}, {get, 0}, {get, 1}, {post, 1}, {delete, 1}, {from_json, 2}, {to_json, 1}]; behaviour_info(_) -> undefined. parse_transform(Forms, _Options) -> %io:format("~p~n", [Forms]), RecordName = rest_record(Forms), RecordFields = record_fields(RecordName, Forms), Forms1 = generate({from_json, 2}, RecordName, RecordFields, Forms), Forms2 = generate({to_json, 1}, RecordName, RecordFields, Forms1), %io:format("~p~n", [Forms2]), Forms2. rest_record([]) -> []; rest_record([{attribute, _, rest_record, RecordName} | _Forms]) -> RecordName; rest_record([_ | Forms]) -> rest_record(Forms). record_field({record_field, _, {atom, _, Field} }, _RecordName) -> %io:format("Case 1: ~p~n", [Field]), Field; record_field({record_field, _, {atom, _, Field}, _Type}, _RecordName) -> %io:format("Case 2: ~p~n", [Field]), Field; record_field({typed_record_field, {record_field, _, {atom, _, Field}, _}, Type}, RecordName) -> Rec = allow(Type), erlang:put({RecordName, Field}, Rec), %case Rec of % undefined -> io:format("Case 3: ~p~n", [Field]); % _ -> io:format("Case 3: ~p, Link: ~p~n", [Field, Rec]) %end, Field. allow({type, _, union, Components}) -> findType(Components); allow(Type) -> findType([Type]). findType([]) -> undefined; findType([{type, _, record, [{atom, _, X}]}|_T]) -> X; findType([{remote_type, _, [{atom, _, _}, {atom, _, X}, _]}|_T]) -> X; findType([_H|T]) -> %io:format("Unknown Type: ~p~n", [_H]), findType(T). record_fields(_RecordName, []) -> []; record_fields(RecordName, [{attribute, _, record, {RecordName, Fields}} | _Forms]) -> [record_field(Field, RecordName) || Field <- Fields]; record_fields(RecordName, [_ | Forms]) -> record_fields(RecordName, Forms). last_export_line(Exports) -> case lists:reverse(Exports) of [{_, Line, _, _} | _] -> Line; _ -> 0 end. generate({FunName, _Arity} = Fun, Record, Fields, Forms) -> Exports = lists:filter( fun({attribute, _, export, _}) -> true; (_) -> false end, Forms), case exported(Fun, Exports) of true -> Forms; false -> Line = last_export_line(Exports), Gen = list_to_atom("generate_" ++ atom_to_list(FunName)), lists:flatten([?MODULE:Gen(export(Form, Fun, Line), Record, Fields) || Form <- Forms]) end. exported(Fun, Exports) -> lists:member(Fun, lists:flatten([E || {attribute, _, export, E} <- Exports])). field_var(Field) -> erlang:list_to_atom("V_" ++ erlang:atom_to_list(Field)). from_json_prelude(Line) -> {clause, Line, [{nil, Line}, {var, Line, 'Acc'}], [], [{var, Line, 'Acc'}]}. from_json_coda(Line) -> {clause, Line, [{cons, Line, {var, Line, '_'}, {var, Line, 'Json'}}, {var, Line, 'Acc'}], [], [{call, Line, {atom, Line, from_json}, [ %{var, Line, 'Json'}, % here is fix for recursive binarized preprocessing to raw X:from_json {call, Line, {remote, Line, {atom, Line, ?MODULE}, {atom, Line, binarize}}, [{var, Line, 'Json'}]}, {var, Line, 'Acc'} ]}]}. from_json_clauses(_, _, []) -> []; from_json_clauses(Line, Record, [Field | Fields]) -> [{clause, Line, [{cons, Line, {tuple, Line, [{bin, Line, [{bin_element, Line, {string, Line, erlang:atom_to_list(Field)}, default, default}]}, {var, Line, field_var(Field)}]}, {var, Line, 'Json'}}, {var, Line, 'Acc'}], [], [{call, Line, {atom, Line, from_json}, [{var, Line, 'Json'}, {record, Line, {var, Line, 'Acc'}, Record, [{record_field, Line, {atom, Line, Field}, {call, Line, {remote, Line, {atom, Line, ?MODULE }, {atom, Line, from_json}}, [{var, Line, field_var(Field)}, {atom, Line, case erlang:get({Record, Field}) of undefined -> Record; FieldType -> FieldType end}]} }]}]}]} | from_json_clauses(Line, Record, Fields)]. generate_from_json({eof, Line}, Record, Fields) -> [{function, Line, from_json, 2, [from_json_prelude(Line)] ++ from_json_clauses(Line, Record, Fields) ++ [from_json_coda(Line)]}, {eof, Line + 1}]; generate_from_json(Form, _, _) -> Form. export({attribute, LastExportLine, export, Exports}, Fun, LastExportLine) -> {attribute, LastExportLine, export, [Fun | Exports]}; export(Form, _, _) -> Form. to_json_cons(Line, []) -> {nil, Line}; to_json_cons(Line, [Field | Fields]) -> {cons, Line, {tuple, Line, [{atom, Line, Field}, {call, Line, {remote, Line, {atom, Line, ?MODULE}, {atom, Line, to_json}}, [{var, Line, field_var(Field)}]}]}, to_json_cons(Line, Fields)}. generate_to_json({eof, Line}, Record, Fields) -> [{function, Line, to_json, 1, [{clause, Line, [{record, Line, Record, [{record_field, Line, {atom, Line, F}, {var, Line, field_var(F)}} || F <- Fields]}], [], [to_json_cons(Line, Fields)]}]}, {eof, Line + 1}]; generate_to_json(Form, _, _) -> Form. from_json(<>, _) -> erlang:binary_to_list(Data); from_json({struct, Props}, X) -> from_json(Props, X); from_json([{Key, _}|_] = Props, X) when Key =/= struct -> X:from_json(binarize(Props), X:new()); from_json(Any, _X) -> Any. atomize([{Key, _}|_]=Props) when Key =/= struct -> lists:map(fun ({K, V}) when erlang:is_atom(K) -> {K, V}; ({K, V}) when erlang:is_binary(K) -> {erlang:list_to_existing_atom(erlang:binary_to_list(K)), V} end, Props); atomize(X) -> X. binarize([{Key, _}|_]=Props) when Key =/= struct -> lists:map(fun ({K, V}) when erlang:is_atom(K) -> {erlang:list_to_binary(erlang:atom_to_list(K)), allowed_value(V)}; ({K, V}) when erlang:is_binary(K) -> {K, allowed_value(V)} end, Props); binarize(X) -> X. allowed_value(X) when erlang:is_reference(X) -> []; allowed_value(X) -> X. to_json(X) when erlang:is_tuple(X) -> Module = erlang:hd(erlang:tuple_to_list(X)), Module:to_json(X); to_json(Data) -> case is_string(Data) of true -> to_binary(Data); false -> json_match(Data) end. json_match([{_, _} | _] = Props) -> [{to_binary(Key), to_json(Value)} || {Key, Value} <- Props]; json_match([_ | _] = NonEmptyList) -> [to_json(X) || X <- NonEmptyList]; json_match(Any) -> Any. is_char(C) -> erlang:is_integer(C) andalso C >= 0 andalso C =< 255. is_string([N | _] = PossibleString) when erlang:is_number(N) -> lists:all(fun is_char/1, PossibleString); is_string(_) -> false. to_binary(A) when erlang:is_atom(A) -> erlang:atom_to_binary(A, latin1); to_binary(B) when erlang:is_binary(B) -> B; to_binary(I) when erlang:is_integer(I) -> to_binary(erlang:integer_to_list(I)); to_binary(F) when erlang:is_float(F) -> erlang:float_to_binary(F, [{decimals, 9}, compact]); to_binary(L) when erlang:is_list(L) -> erlang:iolist_to_binary(L). parse(String) -> %{ok, Tokens, _EndLine} = erl_scan:string(String), {ok, Tokens, _EndLine} = erl_scan:string(String ++ "."), {ok, AbsForm} = erl_parse:parse_exprs(Tokens), {value, Value, _Bs} = erl_eval:exprs(AbsForm, erl_eval:new_bindings()), Value.