Current section
Files
Jump to
Current section
Files
src/bert_google.erl
-module(bert_google).
-export([parse_transform/2]).
-include("bert.hrl").
% mini prelude
c(K) -> list_to_atom(binary_to_list(term_to_binary(K))).
tab(N) -> bert:tab(N).
undup(X) -> sets:to_list(sets:from_list(X)).
j(X) -> lists:concat(X).
j(X,Y) -> string:join(X,Y).
var(K) -> application:get_env(rpc,c(K),[]).
svar(K,V) -> application:set_env(rpc,c(K),V).
ensure() -> Dir = filename:join([?GOOGLE,var(module)]), filelib:ensure_dir(Dir++"/java/"), Dir.
any(M) -> X = ["google","protobuf","Any"], svar({int,M}, [j(X,"/")] ++ var({int,M})), j(X,".").
% export
parse_transform(Forms, _O) ->
lists:map(fun save/1,gen(Forms)),
File = filename:join(ensure(),"1compile.sh"),
file:write_file(File,<<"#!/bin/sh\n\nprotoc *.proto --java_out=java\n">>),
file:change_mode(File, 8#764),
Forms.
gen(Forms) ->
svar({enums,[]}, []),
lists:flatten([ form(F) || F <- Forms ]).
save({[],_}) -> [];
save({File,Bin}) ->
io:format("Generated Protobuf Model: ~p~n",[File]),
Imprint = "// Generated by https://github.com/synrc/bert\n"
"// DO NOT EDIT\n\n", file:write_file(File,iolist_to_binary([Imprint,Bin])).
form({attribute,1,module,M}) -> svar(module,j([M])), {[],[]};
form({attribute,_,type,{Field,{type,_,union,Atoms},[]}}) ->
bert:info(?MODULE,"TYPE: ~p~n",[{Field,Atoms}]),
A = [ X || {_,_,X} <- Atoms ],
svar({deps,Field},[]),
File = filename:join([ensure(),atom_to_list(Field)++".proto"]),
{File,[header(Field),enum(Field,[],A)]};
form({attribute,_,record,{List,T}}) ->
bert:info(?MODULE,"RECORD: ~p~n",[{List,T}]),
svar({deps,List},[]),
case lists:member(List,var(disallowed)) of
true -> [];
_ -> class(List,T)
end;
form(_X) ->
bert:info(?MODULE,"UNKNOWN: ~p~n",[_X]),
{[],[]}.
class(Msg,T) ->
File = filename:join(ensure(),atom_to_list(Msg)++".proto"),
Fields = [
begin {Field,Type,_,Args} = case L of
{_,{_,_,{atom,_,F},V},{_,_,_T,A}} -> {F,_T,V, A};
{_,{_,_,{atom,_,F}}, {type,_,_T,A}} -> {F,_T,[],A};
{_,_,{atom,_,F}, {call,_,_,_}} -> {F,binary,[],[]};
{_,_,{atom,_,F}, {nil,_}} -> {F,binary,[],[]};
{_,_,{atom,_,F}} -> {F,atom, [],[]};
{_,_,{atom,_,F}, {S,_,V}} -> {F,S,V,[]}
end,
bert:info(?MODULE,"DEBUG: ~p~n",[{Field,Type,Args}]),
tab(1) ++ infer(Msg,Type,Args,atom_to_list(Field),lists:concat([Pos]))
end || {L,Pos} <- lists:zip(T,lists:seq(1,length(T))) ],
{File,[header(Msg),enums(),j(["message ",Msg," {\n",Fields,"}\n"])]}.
header(N) ->
bert:info(?MODULE,"TRACE: ~p~n",[var({deps,N})]),
Package = case var(module) of []->[]; M -> "package " ++ M ++ ";\n\n" end,
Internals = undup([j(["import \"", I,".proto\";\n"]) || I <- var({int,N}) ]),
Publics = undup([j(["import public \"",I,".proto\";\n"]) || I <- var({deps,N}) , I/=N]),
lists:concat(
[ "syntax = \"proto3\";\n", Package, Internals,
"option java_generic_services = true;\n",
"option java_multiple_files = true;\n",
"option java_package = \"", N, ".grpc\";\n"
"option java_outer_classname = \"", N, "Cls\";\n", Publics, "\n" ]).
enums() -> j([ begin {_,F} = Name, Enums = var({enum,Name}),
enum(F,"Enum",Enums) end || Name <- var({enums,[]}) ]).
enum(F,Sfx,Enums) ->
X = "enum " ++ j([F,Sfx]) ++ " {\n" ++
[ tab(1) ++ j([Enum]) ++ " = " ++ j([Pos]) ++ ";\n" || {Pos,Enum}
<- lists:zip(lists:seq(0,length(Enums)-1),Enums) ] ++ "}\n\n",
case Enums of [] -> []; _ -> X end.
keyword(_M,list,[{type,_,atom,[]}]) -> "repeated " ++ any(_M);
keyword(_M,list,[{type,_,union,_}]) -> "repeated " ++ any(_M);
keyword(_M,list,[{type,_,record,[{_,_,N}]}]) -> svar({deps,_M}, [N] ++ var({deps,_M})), j(["repeated ", N]);
keyword(_M,list,[{user_type,_,N,[]}]) -> svar({deps,_M}, [N] ++ var({deps,_M})), j(["repeated ", N]);
keyword(_M,record,[{atom,_,N}]) -> svar({deps,_M}, [N] ++ var({deps,_M})), j([N]);
keyword(_M,list, [{type,_,integer,[]}]) -> "repeated int64";
keyword(_M,list,_) -> "repeated " ++ any(_M);
keyword(_M,term,_) -> "bytes";
keyword(_M,integer,_) -> "int64";
keyword(_M,boolean,_) -> "bool";
keyword(_M,atom,_) -> "string";
keyword(_M,binary,_) -> "string";
keyword(_M,union,_) -> "oneof";
keyword(_M,nil,_) -> "bytes";
keyword(_M,N,_) -> svar({deps,_M}, [N] ++ var({deps,_M})), j([N]).
% Message Field Args Pos Name Rest X
infer(_,[],_,_,_) -> [];
infer(M,union,[{type,_,nil,_},{type,_,record,X}],F,P) -> infer(M,record,X,F,P);
infer(M,union,[{type,_,nil,_},{type,_,N,_}=X],F,P) -> infer(M,N,X,F,P);
infer(M,union,T,F,P) ->
A = [ O || {_,_,J,_} = O <- T, J /= nil ],
{Atoms,Rest} = lists:partition(fun ({atom,_,_,_}) -> true; (_) -> false end, A),
svar({enum,{M,F}}, [ X || {_,_,X,_} <- Atoms ]),
svar({enums,[]}, [{M,F}] ++ var({enums,[]})),
bert:info(?MODULE,"INFER: ~p~n",[{M,F,Atoms,Rest}]),
case {Atoms,Rest} of
{[],_} -> simple(M,A,{F,A,P});
{_,[{_,_,nil,[]}]} -> F ++ "Enum " ++ F ++ " = " ++ P ++ ";\n";
{_,[]} -> F ++ "Enum " ++ F ++ " = " ++ P ++ ";\n";
_ -> simple(M,A,{F,A,P}) end;
infer(Message,Type,Args,Field,Pos) ->
lists:concat([keyword(Message,Type,Args)," ",Field," = ",lists:concat([Pos]),";\n"]).
foldr(_,[],{_,_,_},_) -> [];
foldr(M,[{type,_,nil,_}|R],{F,A,P},X) -> foldr(M,R,{A,F,P},X);
foldr(M,[{type,_,record,[{atom,_,N}]}|R],{F,A,P},X) ->
[{infer(M,N, A,j([F,P,X]),j([P,X]))}] ++ foldr(M,R,{F,A,P},X+1);
foldr(M,[{type,_,T,_A}],{F,_,P},X) -> [{infer(M,T,_A,j([F,P,X]),j([P,X]))}];
foldr(M,[{type,_,T,_A}|R],{F,A,P},X) ->
[{infer(M,T,_A,j([F,P,X]),j([P,X]))}] ++ foldr(M,R,{F,A,P},X+1).
simple(M,T,{F,A,P}) ->
Fold = foldr(M,T,{F,A,P},0),
case length(Fold) of
1 -> [{J}] = Fold, J;
_ -> bert:info(?MODULE,"FOLD ~p: ~p~n",[M,Fold]),
j(["oneof ",F," {\n",j(lists:map(fun({X})->tab(2)++X end,Fold)),tab(1),"}\n"]) end.