% Copyright (C) 2014-2019 Olivier Boudeville
%
% This file is part of the Ceylan-WOOPER library.
%
% This library is free software: you can redistribute it and/or modify
% it under the terms of the GNU Lesser General Public License or
% the GNU General Public License, as they are published by the Free Software
% Foundation, either version 3 of these Licenses, or (at your option)
% any later version.
% You can also redistribute it and/or modify it under the terms of the
% Mozilla Public License, version 1.1 or later.
%
% This library is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU Lesser General Public License and the GNU General Public License
% for more details.
%
% You should have received a copy of the GNU Lesser General Public
% License, of the GNU General Public License and of the Mozilla Public License
% along with this library.
% If not, see and
% .
%
% Author: Olivier Boudeville [olivier (dot) boudeville (at) esperide (dot) com]
% Creation date: Wednesday, December 24, 2014
% Centralizes, on behalf of the WOOPER parse transform, the support for instance
% destruction.
%
-module(wooper_instance_destruction).
-export([ manage_destructor/1 ]).
% For the function_info record:
-include("ast_info.hrl").
% For the class_info record:
-include("wooper_info.hrl").
% Shorthands:
-type function_info() :: ast_info:function_info().
-type marker_table() :: ast_info:section_marker_table().
-type compose_pair() :: wooper_parse_transform:compose_pair().
% Extracts any destructor found in the specified function table, interprets that
% information to update the specified class information.
%
% Returns an updated pair thereof.
%
-spec manage_destructor( compose_pair() ) -> compose_pair().
manage_destructor( { FunctionTable, ClassInfo } ) ->
FunIdInfos = table:enumerate( FunctionTable ),
% First, check that no destruct/N with N =/= 1 exists:
{ DestructFunInfo, FilteredFunIdInfos } =
case scan_for_destructors( FunIdInfos ) of
undefined ->
MarkerTable = ClassInfo#class_info.markers,
% None defined, adding thus a basic, do-nothing one:
{ get_default_destructor_info( MarkerTable ), FunIdInfos };
% Found and extracted as:
{ DestrFunInfo, OtherFunIdInfos } ->
% Here the developer has defined destruct/1, yet may or may not
% have exported it:
%
NewDestrFunInfo = case DestrFunInfo#function_info.exported of
[] ->
% Never exported, so exporting it at the standard
% location:
MarkerTable = ClassInfo#class_info.markers,
ExportLocation =
ast_info:get_default_export_function_location(
MarkerTable ),
DestrFunInfo#function_info{
exported=[ ExportLocation ] };
_ ->
DestrFunInfo
end,
{ NewDestrFunInfo, OtherFunIdInfos }
end,
ShrunkFunctionTable = table:new( FilteredFunIdInfos ),
% In all cases a destructor is defined then:
NewClassInfo = ClassInfo#class_info{ destructor=DestructFunInfo },
%trace_utils:debug_fmt( "Destructor info: ~s",
% [ wooper_info:destructor_to_string( DestructFunInfo,
% _DoIncludeForms=true, _IndentationLevel=1 ) ] ),
{ ShrunkFunctionTable, NewClassInfo }.
% Checks arities and extracts any destruct/1 found, returning it and the list of
% remaining pairs, if found, otherwise undefined.
%
% (helper)
%
scan_for_destructors( FunIdInfos ) ->
scan_for_destructors( FunIdInfos, _Acc={ undefined, [] } ).
% (helper)
scan_for_destructors( _FunIdInfos=[], _Acc={ undefined, _AllFunIdInfos } ) ->
% No need to return a list of pairs already known of the caller:
undefined;
% Here Acc is { DestFunInfo, RemainingFunIdInfos }:
scan_for_destructors( _FunIdInfos=[], Acc ) ->
Acc;
scan_for_destructors( _FunIdInfos=[ { { destruct, 1 },
#function_info{ clauses=[],
spec=S } } | _T ],
_Acc ) when S =/= undefined ->
wooper_internals:raise_usage_error( "destructor (destruct/1) has a spec, "
"yet it has never been defined." );
scan_for_destructors( _FunIdInfos=[ { { destruct, 1 }, DestFunInfo } | T ],
_Acc={ undefined, AccFunIdInfos } ) ->
scan_for_destructors( T, { DestFunInfo, AccFunIdInfos } );
scan_for_destructors( _FunIdInfos=[ { { destruct, N }, _DestFunInfo } | _T ],
_Acc ) ->
wooper_internals:raise_usage_error(
"wrong arity for destructor (destruct/1): expected 1, got ~B.", [ N ] );
scan_for_destructors( _FunIdInfos=[ Other | T ],
_Acc={ DestElem, OtherFunIdInfos } ) ->
scan_for_destructors( T, { DestElem, [ Other | OtherFunIdInfos ] } ).
% Returns a function information corresponding to the default destructor, which
% is:
%
% -spec destruct( wooper:state() ) -> wooper:state().
% destruct( State ) ->
% State.
%
-spec get_default_destructor_info( marker_table() ) -> function_info().
get_default_destructor_info( MarkerTable ) ->
Line = 0,
% First, let's define the destructor spec, which is:
% -spec destruct( wooper:state() ) -> wooper:state().
% Corresponds to wooper:state():
StateType = wooper_parse_utils:get_state_type(),
SpecForm = { attribute, Line, spec, { {destruct,1},
[ { type, Line, 'fun',
[ { type, Line, product, _Params=[ StateType ] }, _Result=StateType ]
} ] } },
% Then let's define the destructor function itself, based on:
% destruct( State ) ->
% State.
StateVar = wooper_parse_utils:get_state_var(),
% In AST, we shall have { function, Line, destruct, 1, [ DestructClause ] }:
DestructClause = { clause, Line, _Pattern=[ StateVar ], [],
_Body=[ StateVar ] },
% The spec and definition are to be placed together at this definition
% marker:
%
DefLocation = ast_info:get_default_definition_function_location(
MarkerTable ),
% While the export is to be done in (and will be automatically declared in
% that export form):
%
ExportLocation = ast_info:get_default_export_function_location(
MarkerTable ),
#function_info{ name=destruct,
arity=1,
location=DefLocation,
line=Line,
clauses=[ DestructClause ],
spec={ DefLocation, SpecForm },
callback=false,
exported=[ ExportLocation ] }.