% Copyright (C) 2003-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] % Modular WOOPER header gathering the primitives (exports) to manage the state % of an instance. -export([ setAttribute/3, setAttributes/2, getAttribute/2, getAttributes/2, is_wooper_debug/0, hasAttribute/2, removeAttribute/2, addToAttribute/3, subtractFromAttribute/3, incrementAttribute/2, decrementAttribute/2, toggleAttribute/2, appendToAttribute/3, concatToAttribute/3, deleteFromAttribute/3, addKeyValueToAttribute/4, popFromAttribute/2, wooper_check_undefined/2 ]). % Macros versus functions. % Note that the below macros had to be one-liners, otherwise their actual value % would be the first evaluated in the macro - not the last. % % Indeed 'NewState = ?setAttribute( State, ...)' *and* ' my_function( % ?setAttribute( State, ...)' have to be supported, whereas they respectively % use the first and the last element of a sequence of statements separated with % a comma. % % Indeed, if using a macro with multiple instructions, in the first example % NewState would be bound to the result of the first instruction of the macro, % as shown in: % 1> A=a,b,c. % c % 2> A. % a % instead of the expected final macro result, whereas for the second example the % result sent to my_function would be (as expected) the updated state (c, in the % previous shell output). % Another problem that must be avoided is that if calling for example % '?setAttribute( f(State), name, value )', then we want any side-effect caused % by f/1 to be triggered exactly once. % % For example in the setAttribute/3 macros defined below (and now % commented-out), multiple references to (State) would have resulted in as many % calls to f/1, which is not correct. % Therefore we came to the conclusion that macros could not fulfill our needs, % and we defined full-blown functions instead, even if we incur the performance % penalty of an additional function call. To alleviate this overhead, we % recommend the compiler to inline these functions. % % So finally '?setAttribute( AState, name, value )' should be transformed into % 'setAttribute( AState, name, value )'. % Newer macros are defined for backward compatibility, resulting in the same % function call. They should not be used anymore, their purpose is just to % support unchanged legacy code. % Finally, with an upcoming version of WOOPER making use of parse transforms and % better state storage, attributes will be used thanks to the same function % calls, while fully removing their performance penalty. % Macro-based implementations (except the getAttr/1 and getSender/0 macros) and % legacy macros could be removed in next versions. % Returns the value associated to specified named-designated attribute, if % found, otherwise triggers a case clause error. % % Ex: MyCount = ?getAttr(count) % % This macro is usually more useful than the getAttribute function, as one % generally wants to retrieve an attribute already available in the 'State' % parameter of a method (otherwise that value is available through a bound % variable in the method body). % % Therefore the use of a variable named 'State' can often be implied. % % Beware to the implicit use of the 'State' variable: in some cases other states % should be used; due to this implicit variable, getAttr must remain a macro. % % See the longer getAttribute/2 function. % % Definitively not a legacy macro. % -define( getAttr(AttributeName), getAttribute( State, (AttributeName) ) ). % Returns the value associated to each of the specified named-designated % attributes (if found, otherwise triggers a case clause error), in the order of % their specification. % % Ex: [ MyCount, MyAge, MyIdeas ] = ?getAttrList([ count, age, ideas ]) % % This macro is usually more useful than the getAttributes function, as one % generally wants to retrieve attributes already available in the 'State' % parameter of a method (otherwise these values are available through a bound % variable in the method body). % % Therefore the use of a variable named 'State' can often be implied. % % Beware to the implicit use of the 'State' variable: in some cases other states % should be used; due to this implicit variable, getAttr must remain a macro. % % See the longer getAttributes/2 function. % % Definitively not a legacy macro. % -define( getAttrList(AttributeNameList), getAttributes( State, (AttributeNameList) ) ). % Returns the sender of the request. % % Must be a macro, due to the implied 'State' variable. % -define( getSender(), State#state_holder.request_sender ). % Checks that the value of specified attribute is 'undefined'. % % Triggers an exception otherwise. % % Note: operates on a state called 'State', thus must be a macro. % % The check could be disabled in debug mode. % % Not a macro per se, in order to avoid unintended multiple function calls, % warnings about unused variables or, more probably, unintended pattern matches. % -define( checkUndefined( AttributeName ), wooper_check_undefined( (AttributeName), State ) ). % Uncomment if old-style attribute management macros are to be enabled: % (by default we do not want to support them anymore) %-define(use_legacy_macros,). -ifdef( use_legacy_macros ). % These macros are defined (if enabled) for backward compatibility only: % Faulty macro (risk of side-effects being executed more than once): % Sets specified attribute of the instance to the specified value, based from % specified state. % % Returns an updated state. % % Always succeeds. % % See also: the setAttributes macro to set more than one attribute at a time. % %-define( setAttribute( State, AttributeName, AttributeValue ), % (State)#state_holder{ % attribute_table = ?wooper_table_type:add_entry( % (AttributeName), % (AttributeValue), % (State)#state_holder.attribute_table ) % } %). % Macro defined for backward compatibility only: -define( setAttribute( State, AttributeName, AttributeValue ), setAttribute( (State), (AttributeName), (AttributeValue) ) ). % Faulty macro (risk of side-effects being executed more than once): % Sets a list of attribute/value pairs in specified state. % % The expected parameter is a list of pairs (2-element tuples), each pair % containing in first position the attribute name and in second one the % attribute value. % % Returns an updated state. % % Always succeeds. % % See also: setAttribute/3. % %-define( setAttributes( State, ListOfAttributePairs ), % (State)#state_holder{ % attribute_table = ?wooper_table_type:add_entries( % (ListOfAttributePairs), % (State)#state_holder.attribute_table ) % } %). % Macro defined for backward compatibility only: -define( setAttributes( State, ListOfAttributePairs ), setAttributes( (State), (ListOfAttributePairs) ) ). % Faulty macro (risk of side-effects being executed more than once): % Tells whether specified attribute exists, returns true or false. % % Note: usually the best practise is to set all possible attributes from the % constructor, either to an appropriate value or to 'undefined', instead of % having instances with or without a given attribute. % % Note: not expected to be ever used, as all attributes should be defined % directly in the constructor, hence no attribute could appear later, if this % good practise is respected. % %-define( hasAttribute( State, AttributeName ), % ?wooper_table_type:has_entry( (AttributeName), % (State)#state_holder.attribute_table ) ). % Macro defined for backward compatibility only: -define( hasAttribute( State, AttributeName ), hasAttribute( (State), (AttributeName) ) ). % Faulty macro (risk of side-effects being executed more than once): % Returns the value associated to specified named-designated attribute, if % found, otherwise triggers a case clause crash. % % Note: almost never used, as either the attribute can be obtained with % getAttr/1 (as externally defined) or it is already bound to a variable. % % See also: the getAttr/1 shorthand. % %-define( getAttribute( State, AttributeName ), % ?wooper_table_type:get_value( (AttributeName), % (State)#state_holder.attribute_table ) ). % Macro defined for backward compatibility only: -define( getAttribute( State, AttributeName ), getAttribute( (State), (AttributeName) ) ). % Faulty macro (risk of side-effects being executed more than once): % Returns an updated state not having anymore specified attribute. % % No error is triggered if the specified attribute was not existing. % %-define( removeAttribute( State, AttributeName ), % (State)#state_holder{ % attribute_table = ?wooper_table_type:remove_entry( (AttributeName), % (State)#state_holder.attribute_table ) % } %). % Macro defined for backward compatibility only: -define( removeAttribute( State, AttributeName ), removeAttribute( (State), (AttributeName) ) ). % Faulty macro (risk of side-effects being executed more than once): % Adds specified value to specified attribute, supposed to be a number. % % A case clause is triggered if the attribute did not exist, a bad arithm is % triggered if no addition can be performed on the attribute value. %-define( addToAttribute( State, AttributeName, Value ), % (State)#state_holder{ % attribute_table = ?wooper_table_type:add_to_entry( % (AttributeName), % (Value), % (State)#state_holder.attribute_table ) % } %). % Macro defined for backward compatibility only: -define( addToAttribute( State, AttributeName, Value ), addToAttribute( (State), (AttributeName), (Value) ) ). % Faulty macro (risk of side-effects being executed more than once): % Subtracts specified value from specified attribute, supposed to be a number. % % A case clause is triggered if the attribute did not exist, a bad arithm is % triggered if no subtraction can be performed on the attribute value. % %-define( subtractFromAttribute( State, AttributeName, Value ), % (State)#state_holder{ % attribute_table = ?wooper_table_type:subtract_from_entry( % (AttributeName), % (Value), % (State)#state_holder.attribute_table ) % } %). % Macro defined for backward compatibility only: -define( subtractFromAttribute( State, AttributeName, Value ), subtractFromAttribute( (State), (AttributeName), (Value) ) ). % Faulty macro (risk of side-effects being executed more than once): % Returns an updated state in which specified boolean attribute is toggled: % if true will be false, if false will be true. % A case clause is triggered if the attribute does not exist or it is not a % boolean value. %-define( toggleAttribute( State, BooleanAttributeName ), % (State)#state_holder{ % attribute_table = ?wooper_table_type:toggle_entry( % (BooleanAttributeName), % (State)#state_holder.attribute_table ) % } %). % Macro defined for backward compatibility only: -define( toggleAttribute( State, BooleanAttributeName ), toggleAttribute( (State), (BooleanAttributeName) ) ). % Faulty macro (risk of side-effects being executed more than once): % Appends specified element to specified attribute, supposed to be a list. % A case clause is triggered if the attribute did not exist. % % Note: no check is performed to ensure the attribute is a list indeed, and the % operation will not complain if not. % %-define( appendToAttribute( State, AttributeName, Element ), % % (State)#state_holder{ % attribute_table = ?wooper_table_type:append_to_entry( % (AttributeName), % (Element), % (State)#state_holder.attribute_table ) % } %). % Macro defined for backward compatibility only: -define(appendToAttribute( State, AttributeName, Element ), appendToAttribute( (State), (AttributeName), (Element) ) ). % Faulty macro (risk of side-effects being executed more than once): % Deletes the first match of specified element from specified attribute, % supposed to be a list. % % A case clause is triggered if the attribute did not exist. % % If the element is not in the specified list, the list will not be modified. % %-define( deleteFromAttribute( State, AttributeName, Element ), % % (State)#state_holder{ % attribute_table = ?wooper_table_type:delete_from_entry( % (AttributeName), % (Element), % (State)#state_holder.attribute_table ) % } %). % Macro defined for backward compatibility only: -define( deleteFromAttribute( State, AttributeName, Element ), deleteFromAttribute( (State), (AttributeName), (Element) ) ). % Faulty macro (risk of side-effects being executed more than once): % Assumes the specified attribute is a hashtable and adds the specified % key/value pair to it. % % Several lines compacted into a bit impressive one-liner. % %-define( addKeyValueToAttribute( State, AttributeName, Key, Value ), % % (State)#state_holder{ % attribute_table = ?wooper_table_type:add_entry( % (AttributeName), % ?wooper_table_type:add_entry( (Key), (Value), % ?wooper_table_type:get_value( (AttributeName), % (State)#state_holder.attribute_table ) ), % (State)#state_holder.attribute_table ) % } %). % Macro defined for backward compatibility only: -define( addKeyValueToAttribute( State, AttributeName, Key, Value ), addKeyValueToAttribute( (State), (AttributeName), (Key), (Value) ) ). % Faulty macro (risk of side-effects being executed more than once): % Removes the head from specified attribute, supposed to be a list, and returns % a tuple { NewState, PoppedHead }. % % For example, if the attribute 'my_list' contains [5,8,3], executing: % '{ PoppedState, Head } = ?popFromAttribute( State, my_list )' % returns a state whose my_list attribute is [8,3] and a value Head = 5. % % A case clause is triggered if the attribute did not exist. % % Note: This cannot be a one-line macro, it has to be a function. % %-define( popFromAttribute( State, AttributeName ), % wooper_pop_from_attribute( (State), (AttributeName) ) %). % Macro defined for backward compatibility only: -define( popFromAttribute( State, AttributeName ), popFromAttribute( (State), (AttributeName) ) ). -endif. % use_legacy_macros