Packages

Erlang Record Extender Parse Transform

Current section

Files

Jump to
rekt src rekt.erl
Raw

src/rekt.erl

%% Copyright 2017 Jesse Gumm
%% MIT LICENSE
-module(rekt).
%% API exports
-export([parse_transform/2]).
%%====================================================================
%% API functions
%%====================================================================
parse_transform(Forms, _Options) ->
%io:fwrite("Forms: ~p",[Forms]),
NewForms = transform_until_done(Forms),
FinalForms = replace_unhandled_extends_with_errors(NewForms),
%io:fwrite("Final Forms: ~p",[FinalForms]),
FinalForms.
%%====================================================================
%% Internal functions
%%====================================================================
transform_until_done(Forms) ->
Recs = extract_records(Forms),
NewForms = replace_extends(Forms, Recs),
case NewForms==Forms of
true -> NewForms;
false -> transform_until_done(NewForms)
end.
replace_unhandled_extends_with_errors(Forms) ->
lists:map(fun
({attribute, LineNum, extend, {SourceRec, _NewRec, _Fields}}) ->
ErrMsg = lists:flatten(io_lib:format("rekt: Unable to extend undefined record '~p'", [SourceRec])),
{error, {LineNum, erl_parse, ErrMsg}};
(Other) ->
Other
end, Forms).
extract_records(Forms) ->
[Rec || {attribute, _, record, Rec} <- Forms].
replace_extends(Forms, Recs) ->
lists:map(fun
({attribute, LineNum, extend, {SourceRec, NewRec, Fields}}=RawFull) ->
replace_extend(Recs, LineNum, SourceRec, NewRec, Fields, RawFull);
(Other) ->
Other
end, Forms).
replace_extend(Recs, LineNum, SourceRec, NewRec, Fields, RawFull) ->
case lists:keyfind(SourceRec, 1, Recs) of
{_, OrigFields} ->
NewFields = [field_to_form(F, LineNum) || F <- Fields],
FullFields = merge_fields(OrigFields, NewFields),
{attribute, LineNum, record, {NewRec, FullFields}};
false ->
RawFull
end.
field_to_form(Field, LineNum) when is_atom(Field) ->
{record_field, LineNum, {atom, LineNum, Field}};
field_to_form({Field, Value}, LineNum) when is_atom(Field) ->
FormattedValue = erl_parse:abstract(Value, [{line, LineNum}]),
{record_field, LineNum, {atom, LineNum, Field}, FormattedValue};
field_to_form({Field, Value, TypeStr}, LineNum) ->
%% Types not implemented yet
Str = lists:flatten(io_lib:format("-record(a, {~p=~p :: ~s}).", [Field, Value, TypeStr])),
{attribute, _, record, {_Name, [FieldDef]}} = merl:quote(LineNum, Str),
FieldDef.
merge_fields(OrigFields, NewFields) ->
lists:foldl(fun(NewField, AccFields) ->
replace_or_append_field(NewField, AccFields)
end, OrigFields, NewFields).
replace_or_append_field(NewField, []) ->
[NewField];
replace_or_append_field(NewField, [CurField|Rest]) ->
NewFieldName = extract_field_name(NewField),
CurFieldName = extract_field_name(CurField),
case NewFieldName == CurFieldName of
true ->
%io:fwrite("Replacing: ~n >>>> ~p~n with~n <<<< ~p~n",[CurField, NewField]),
[NewField | Rest];
false ->
[CurField | replace_or_append_field(NewField, Rest)]
end.
extract_field_name({typed_record_field, Rec, _}) ->
extract_field_name(Rec);
extract_field_name({record_field, _, {atom, _, FieldName}}) ->
FieldName;
extract_field_name({record_field, _, {atom, _, FieldName}, _Default}) ->
FieldName.