| 1 | % (c) 2009-2017 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, | |
| 2 | % Heinrich Heine Universitaet Duesseldorf | |
| 3 | % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) | |
| 4 | ||
| 5 | :- module(units, [plugin_info/1]). | |
| 6 | ||
| 7 | plugin_info([ | |
| 8 | % The name of the Plugin | |
| 9 | name = 'Unit Analysis' | |
| 10 | % Author is not used yet | |
| 11 | ,author = 'Sebastian Krings' | |
| 12 | % marks the plugin as unstable or stable | |
| 13 | ,status = stable | |
| 14 | % A list of supported file extensions together with a file description | |
| 15 | % (They are not used here because the plugin is used for an already loaded | |
| 16 | % file, see plugin_for_modes) | |
| 17 | % ,file_extensions = [ ('Plugin demonstration spec', ['.xtl2', '.PPP']) ] | |
| 18 | % The predicate that is used to load a file. The first argument is the | |
| 19 | % filename, the second the file extension (like '.mch'). | |
| 20 | %,load_file = load_spec/1 | |
| 21 | % The transition skeleton initialises a transition variable in such | |
| 22 | % way that only a part of all transitions match it. It is used to | |
| 23 | % separate time outs and maximum number of computations for several | |
| 24 | % transition classes (like different operations). | |
| 25 | %,transition_skeleton = trans_skel/1 | |
| 26 | % The predicate that computes a transition in the state-space, its | |
| 27 | % arguments are SourceState,Operation,DestinationState,ListOfOperationInfos. | |
| 28 | ,transition = do_transition/4 | |
| 29 | % The predicate that computes an initial state, its arguments are | |
| 30 | % Operation,DestinationState,ListOfOperationInfos. | |
| 31 | ,initialisation = do_initialisation/3 | |
| 32 | % A predicate that returns a property for a given state, its arguments | |
| 33 | % are State,Property. | |
| 34 | ,state_property = find_state_property/2 | |
| 35 | % A predicate that receives a list of expressions,predicates and | |
| 36 | % predicates on transitions as input (list of codes) and returns | |
| 37 | % the parsed expressions. | |
| 38 | % Its arguments are ListOfExpressionStrings,ListOfPredicateStrings, | |
| 39 | % ListOfTransitionPredicateStrings,ListOfExpressions,ListOfPredicates, | |
| 40 | % ListOfTransitionPredicates. | |
| 41 | %,parser = parse_spec/6 | |
| 42 | % A predicate that computes a value of an expression (returned by | |
| 43 | % the parser above) in a state. Its arguments are Expression,State | |
| 44 | %,compute_expression = compute/2 | |
| 45 | % A predicate that evaluates a predicate (returned by the parser above) | |
| 46 | % in a state. Its arguments are Predicate,State | |
| 47 | %,evaluate_predicate = eval/2 | |
| 48 | % A predicate that evaluates a predicate (returned by the parser above) | |
| 49 | % for a transition. Its arguments are Predicate,Transition | |
| 50 | %,evaluate_transition = evaltrans/2 | |
| 51 | % A predicate that checks if a state is initialised. Its argument is a State | |
| 52 | %,is_initialised_state = is_init/1 | |
| 53 | % Pretty-print a transition, the first argument is the transition, the | |
| 54 | % second -the result- is a list of codes. | |
| 55 | ,prettyprint_transition = pp_trans/2 | |
| 56 | % Pretty-print a property, the first argument is the property, the | |
| 57 | % second -the result- is a list of codes. | |
| 58 | ,prettyprint_property = pp_prop/2 | |
| 59 | ,prettyprint_value = pp_value/2 | |
| 60 | % Syntax colouring is defined by specifying a list of rules, each | |
| 61 | % containing of a regular expression and a colour class | |
| 62 | %,syntax_colouring = [pair('/\\*', '\\*/',syntax_comment), | |
| 63 | % expression('(?=\\m)(start|trans|prop)(?=\\M)', syntax_keyword), | |
| 64 | % expression('(?=\\m)(true|fail|atomic|nonvar|var|functor|op|is|ground|member|append|length)(?=\\M)|=', syntax_type), | |
| 65 | % expression(':-|!|-->|;|\\.', syntax_assignment), | |
| 66 | % expression('%(.*)', syntax_comment)] | |
| 67 | ,plugin_for_modes = [b] | |
| 68 | ,plugin_init=plugin_init/0 | |
| 69 | ,new_transition_notification=notify_new_transition/4 | |
| 70 | ,check_invariant=do_check_invariant/2 | |
| 71 | ,preferences=preference/4 | |
| 72 | ,output=output/3 | |
| 73 | ,internal_representation=internal_representation/1 | |
| 74 | ]). | |
| 75 | ||
| 76 | :- public do_transition/4, do_initialisation/3, find_state_property/2,pp_trans/2, pp_prop/2, pp_value/2. | |
| 77 | :- public plugin_init/0,notify_new_transition/4,do_check_invariant/2,preference/4,output/3,internal_representation/1. | |
| 78 | ||
| 79 | :- use_module(library(samsort)). | |
| 80 | :- use_module(library(lists)). | |
| 81 | ||
| 82 | :- use_module(probsrc(tools)). | |
| 83 | :- use_module(probsrc(specfile)). | |
| 84 | :- use_module(probsrc(state_space)). | |
| 85 | :- use_module(probsrc(bmachine)). | |
| 86 | :- use_module(probsrc(preferences)). | |
| 87 | :- use_module(probsrc(bmachine)). | |
| 88 | :- use_module(probsrc(bmachine_structure)). | |
| 89 | :- use_module(probsrc(pragmas)). | |
| 90 | :- use_module(probsrc(coverage_tools_annotations),['$NOT_COVERED'/1]). | |
| 91 | :- use_module(probsrc(error_manager)). | |
| 92 | ||
| 93 | :- use_module(units_interpreter). | |
| 94 | :- use_module(units_interpreter_helpers). | |
| 95 | :- use_module(units_prettyprint). | |
| 96 | :- use_module(units_tools). | |
| 97 | :- use_module(units_alias). | |
| 98 | :- use_module(units_domain). | |
| 99 | ||
| 100 | :- use_module(probsrc(module_information)). | |
| 101 | :- module_info(group,plugin_units). | |
| 102 | :- module_info(description,'Units Plugin: This is an experimental plugin that analyses physical units stored in pragmas.'). | |
| 103 | ||
| 104 | :- dynamic iterations/1, static_result/2, units_error_context/1, tcltk_replay_sequence/1. | |
| 105 | ||
| 106 | preference(fixpoint_on_load, true, 'Perform Static Fix-Point Check on Startup',bool). | |
| 107 | preference(fixpoint_halt_on_error, true, 'Halt Fix-Point Search on Error',bool). | |
| 108 | preference(fixpoint_max_iterations, 100, 'Max. Number of Iterations in Fix-Point Check', nat1). | |
| 109 | ||
| 110 | output('Static Analysis Result', X, [tcltk,cli]) :- | |
| 111 | (static_result(FixPointState,UnboundInConstraint) | |
| 112 | -> pp_fixpoint_result(FixPointState,UnboundInConstraint,Output), | |
| 113 | atom_codes(Output,X) | |
| 114 | ; X = "no result yet"), | |
| 115 | '$NOT_COVERED'('GUI code not used in automatic tests'). | |
| 116 | ||
| 117 | output('Grounded Result State', X, [eclipse]) :- | |
| 118 | findall(Error,get_error(incorrect_unit_definition,Error),ListOfErrors), | |
| 119 | ListOfErrors \= [], !, | |
| 120 | % we have something to report. skip the rest | |
| 121 | reset_errors, | |
| 122 | X = error(ListOfErrors). | |
| 123 | ||
| 124 | output('Grounded Result State', state(X), [eclipse]) :- | |
| 125 | (static_result(FixPointState,_UnboundInConstraint) | |
| 126 | -> maplist(pp(eventb),FixPointState,X) | |
| 127 | ; X = []). | |
| 128 | ||
| 129 | plugin_init :- | |
| 130 | units_interpreter_helpers:retractall(cached_global_type(_,_)), | |
| 131 | retractall(tcltk_replay_sequence(_)), | |
| 132 | reset_tcltk_replay_sequence, | |
| 133 | state_space:state_space_initialise, | |
| 134 | (preference(plugin(units,fixpoint_on_load),true) | |
| 135 | -> static_pre_check | |
| 136 | ; true), | |
| 137 | (replay_tcltk_sequence ; write('Could not replay Tcl/Tk Operation Sequence!'), nl). | |
| 138 | ||
| 139 | static_pre_check :- | |
| 140 | % pre check can only be performed on an initialised machine | |
| 141 | bmachine:bmachine_is_precompiled, | |
| 142 | statistics(walltime,[Start|_]), | |
| 143 | retractall(iterations(_)), | |
| 144 | assert(iterations(0)), | |
| 145 | do_initialisation(Operation, State, [initialisation]), | |
| 146 | append_to_tcltk_replay_sequence(Operation), | |
| 147 | to_fixpoint(State,FixPointState), | |
| 148 | retractall(static_result(_,_)), | |
| 149 | extract_unbound_in_constraint(FixPointState,Unbound), | |
| 150 | assert(static_result(FixPointState,Unbound)), | |
| 151 | insert_result_in_machine, | |
| 152 | iterations(It), | |
| 153 | statistics(walltime, [End|_]), | |
| 154 | Time is End - Start, | |
| 155 | format('Fixpoint algorithm stopped after ~w iterations and ~w milliseconds ~n', [It, Time]). | |
| 156 | static_pre_check :- write('Pre-Check failed!\n'), '$NOT_COVERED'('This should not happen'). | |
| 157 | ||
| 158 | extract_unbound_in_constraint([],[]). | |
| 159 | extract_unbound_in_constraint([bind(Var,Val)|T], [Var|RT]) :- | |
| 160 | involved_in_constraint(Val), | |
| 161 | extract_unbound_in_constraint(T,RT). | |
| 162 | extract_unbound_in_constraint([bind(_Var,_Val)|T], RT) :- | |
| 163 | extract_unbound_in_constraint(T,RT). | |
| 164 | ||
| 165 | to_fixpoint(State,FixPointState) :- | |
| 166 | retract(iterations(LastIt)), preference(plugin(units,fixpoint_max_iterations),MaxIt), | |
| 167 | LastIt < MaxIt | |
| 168 | -> CurIt is LastIt + 1, assert(iterations(CurIt)), to_fixpoint2(State,FixPointState) | |
| 169 | ; State=FixPointState, add_warning(units, 'Warning: Fixpoint algorithm stopped, because iteration limit was reached\n'). | |
| 170 | ||
| 171 | to_fixpoint2(concrete_constants(State),FixPointState) :- | |
| 172 | !, do_transition(concrete_constants(State),Op,PossibleFixPointState,_Op2), | |
| 173 | append_to_tcltk_replay_sequence(Op), | |
| 174 | to_fixpoint(PossibleFixPointState,FixPointState). | |
| 175 | to_fixpoint2(State,FixPointState) :- | |
| 176 | findall(Operation, do_transition(State,_,_,[op(Operation)]), ListOfOperations), | |
| 177 | copy_term(State,StoredState), | |
| 178 | all_operations(ListOfOperations,State,PossibleFixPointState), | |
| 179 | ( | |
| 180 | another_iteration(StoredState,PossibleFixPointState) | |
| 181 | -> to_fixpoint(PossibleFixPointState,FixPointState) | |
| 182 | ; FixPointState = PossibleFixPointState | |
| 183 | ). | |
| 184 | ||
| 185 | another_iteration(StoredState,PossibleFixPointState) :- | |
| 186 | \+(halt_on_error), state_was_updated(StoredState,PossibleFixPointState). | |
| 187 | ||
| 188 | halt_on_error :- | |
| 189 | preference(plugin(units,fixpoint_halt_on_error),true), | |
| 190 | state_error_exists. | |
| 191 | ||
| 192 | ||
| 193 | all_operations([],State,State). | |
| 194 | all_operations([Operation|T],InState,OutState) :- | |
| 195 | copy_term(InState,StoredInState), | |
| 196 | (do_transition(InState,OpSpec,IntermediateState,[op(Operation)])), !, | |
| 197 | (state_was_updated(StoredInState,IntermediateState) | |
| 198 | -> append_to_tcltk_replay_sequence(OpSpec) | |
| 199 | ; true),%format('Operation ~w did not update~nInState:~n~w~nIntermediateState:~n~w~n', [OpSpec,StoredInState,IntermediateState])), | |
| 200 | (halt_on_error | |
| 201 | -> OutState=IntermediateState | |
| 202 | ; all_operations(T,IntermediateState,OutState)). | |
| 203 | all_operations([_Operation|T],InState,OutState) :- | |
| 204 | all_operations(T,InState,OutState), | |
| 205 | (halt_on_error | |
| 206 | -> OutState=InState | |
| 207 | ; all_operations(T,InState,OutState)), | |
| 208 | add_error(units_all_operations, 'operation could not be evaluated'), | |
| 209 | '$NOT_COVERED'('This should not happen'). | |
| 210 | ||
| 211 | do_initialisation(OpSetup, concrete_constants(ConstantsState), [initialisation]) :- | |
| 212 | bmachine:bmachine_is_precompiled, | |
| 213 | b_machine_has_constants_or_properties, !, | |
| 214 | set_units_error_context(operation('SETUP_CONSTANTS',ConstantsState)), | |
| 215 | init_user_defined_alias, | |
| 216 | b_get_machine_constants(Constants), | |
| 217 | units_build_initial_state(Constants,ConstantsState), | |
| 218 | b_get_properties_from_machine(Properties), | |
| 219 | units_check_boolean_expression(Properties,ConstantsState,[]), | |
| 220 | ((get_preference(show_initialisation_arguments,true),extract_value_list(ConstantsState,Vals)) | |
| 221 | -> safe_univ(OpSetup,['$setup_constants'|Vals]) | |
| 222 | ; (OpSetup = '$setup_constants') | |
| 223 | ). | |
| 224 | ||
| 225 | do_initialisation(Operation,StateOut,[initialisation]) :- | |
| 226 | bmachine:bmachine_is_precompiled, | |
| 227 | \+ b_machine_has_constants_or_properties, !, | |
| 228 | set_units_error_context(operation('$initialise_machine',StateOut)), | |
| 229 | init_user_defined_alias, | |
| 230 | b_get_machine_variables(Variables), | |
| 231 | units_build_initial_state(Variables,VariablesState), | |
| 232 | b_get_initialisation_from_machine(Init,_OType), | |
| 233 | units_execute_statement(Init,[],VariablesState,StateOut,_Path), | |
| 234 | specfile:create_initialisation_operation(StateOut,Operation). | |
| 235 | ||
| 236 | do_transition(concrete_constants(StateIn),Operation,StateOut,[op(Operation)]) :- | |
| 237 | bmachine:bmachine_is_precompiled, !, | |
| 238 | set_units_error_context(operation('$initialise_machine',StateIn)), | |
| 239 | b_get_machine_variables(Variables), | |
| 240 | units_build_initial_state(Variables,VariablesState), | |
| 241 | append(StateIn,VariablesState,State), | |
| 242 | b_get_initialisation_from_machine(Init,_OType), | |
| 243 | units_execute_statement(Init,[],State,UnsortedStateOut,_Path), | |
| 244 | samsort(UnsortedStateOut,StateOut), | |
| 245 | specfile:create_initialisation_operation(StateOut,Operation). | |
| 246 | ||
| 247 | do_transition(State,OpSpec,NewState,[op(Operation)]) :- | |
| 248 | bmachine:bmachine_is_precompiled, | |
| 249 | b_top_level_operation(Operation), | |
| 250 | set_units_error_context(operation(Operation,State)), | |
| 251 | units_find_transition(Operation, State, NewState, Paras, Results), | |
| 252 | (Paras \= [] | |
| 253 | -> OpSpec1 =.. [Operation|Paras] | |
| 254 | ; OpSpec1 = Operation | |
| 255 | ), | |
| 256 | (Results \= [] | |
| 257 | -> OpSpec = '-->'(OpSpec1,Results) | |
| 258 | ; OpSpec = OpSpec1 | |
| 259 | ), | |
| 260 | execute_invariant(NewState). | |
| 261 | ||
| 262 | notify_new_transition(FromID,TransID,DestID,_Exists) :- | |
| 263 | %write(notify_new_transition(FromID,TransID,DestID,Exists)),nl, | |
| 264 | state_space:transition(FromID,Action,TransID,DestID), | |
| 265 | set_units_error_context(operation(Action,_)), | |
| 266 | visited_expression(DestID,State), | |
| 267 | check_state_add_error(DestID,State). | |
| 268 | ||
| 269 | find_state_property(State,Property):- | |
| 270 | member(bind(Var,Val),State), | |
| 271 | Property = '='(Var,Val). | |
| 272 | ||
| 273 | pp_trans(Transition,Pretty) :- | |
| 274 | units_pp_trans(Transition,Pretty). | |
| 275 | ||
| 276 | pp_prop(Property,Pretty) :- | |
| 277 | units_pp_prop(Property,Pretty). | |
| 278 | ||
| 279 | pp_value(Value,Pretty) :- | |
| 280 | units_pp_value(Value,Pretty). | |
| 281 | ||
| 282 | do_check_invariant(ID,_CurState) :- | |
| 283 | %write(do_check_invariant(ID,CurState)), nl, | |
| 284 | invariant_violated(ID), !. | |
| 285 | do_check_invariant(ID,CurState) :- | |
| 286 | set_units_error_context(checking_invariant), | |
| 287 | state_corresponds_to_initialised_b_machine(CurState,CurBState), | |
| 288 | execute_invariant(CurBState), | |
| 289 | set_invariant_checked(ID). | |
| 290 | ||
| 291 | execute_invariant(CurState) :- | |
| 292 | set_units_error_context(checking_invariant), | |
| 293 | b_get_properties_from_machine(Properties), | |
| 294 | units_check_boolean_expression(Properties,CurState,[]), | |
| 295 | b_get_invariant_from_machine(I), | |
| 296 | units_check_boolean_expression(I,[],CurState), | |
| 297 | set_units_error_context(checking_assertions), | |
| 298 | b_get_static_assertions_from_machine(StaticAssertions), | |
| 299 | units_check_list_of_boolean_expressions(StaticAssertions,[],CurState), | |
| 300 | b_get_dynamic_assertions_from_machine(DynamicAssertions), | |
| 301 | units_check_list_of_boolean_expressions(DynamicAssertions,[],CurState). | |
| 302 | ||
| 303 | internal_representation(Rep) :- | |
| 304 | full_b_machine(BMachine), | |
| 305 | (static_result(_,_) -> true ; static_pre_check), | |
| 306 | translate:translate_machine1(BMachine,(0,RepWithoutGlobals),(_,[])), | |
| 307 | findall(alias(AliasName,Definition), | |
| 308 | global_pragma(unit_alias,[AliasName|Definition]), | |
| 309 | List), | |
| 310 | global_pragma_output(List,OutputCodes), | |
| 311 | append(OutputCodes,RepWithoutGlobals,Rep), | |
| 312 | '$NOT_COVERED'('GUI code not used in automatic tests'). | |
| 313 | ||
| 314 | global_pragma_output([],[]) :- '$NOT_COVERED'('GUI code not used in automatic tests'). | |
| 315 | global_pragma_output([alias(AliasName,Definition)|T],Codes) :- | |
| 316 | global_pragma_output(T,CodesTemp), | |
| 317 | insert_spacer_and_join(Definition,DefinitionCodes), | |
| 318 | ajoin(['/*@ unit_alias ', AliasName, ' ', DefinitionCodes, ' */'], CodePragma), | |
| 319 | safe_atom_codes(CodePragma,CodePragmaCodes), | |
| 320 | append(CodePragmaCodes,CodesTemp,Codes), | |
| 321 | '$NOT_COVERED'('GUI code not used in automatic tests'). | |
| 322 | ||
| 323 | insert_result_in_machine :- | |
| 324 | write('Pre-Check finished. Inserting results in machine'), nl, | |
| 325 | static_result(Result,_), | |
| 326 | full_b_machine(BMachine), | |
| 327 | get_section(abstract_variables,BMachine,AbsVarsContent), | |
| 328 | expand_variables(AbsVarsContent,ExpandedAbsVarsContent,Result), | |
| 329 | write_section(abstract_variables,ExpandedAbsVarsContent,BMachine,BMachine2), | |
| 330 | get_section(concrete_variables,BMachine2,ConcVarsContent), | |
| 331 | expand_variables(ConcVarsContent,ExpandedConcVarsContent,Result), | |
| 332 | write_section(concrete_variables,ExpandedConcVarsContent,BMachine2,BMachine3), | |
| 333 | get_section(abstract_constants,BMachine3,AbsConstContent), | |
| 334 | expand_variables(AbsConstContent,ExpandedAbsConstContent,Result), | |
| 335 | write_section(abstract_constants,ExpandedAbsConstContent,BMachine3,BMachine4), | |
| 336 | get_section(concrete_constants,BMachine4,ConcConstContent), | |
| 337 | expand_variables(ConcConstContent,ExpandedConcConstContent,Result), | |
| 338 | write_section(concrete_constants,ExpandedConcConstContent,BMachine4,BMachine5), | |
| 339 | retractall(bmachine:machine(_,_)), | |
| 340 | bmachine:assert_main_machine(BMachine5). | |
| 341 | ||
| 342 | expand_variables([],[],_Result). | |
| 343 | expand_variables([b(identifier(X),Type,Infos)|T],[b(identifier(X),Type,Infos2)|T2],Result) :- | |
| 344 | member(bind(X,Val),Result), !, | |
| 345 | ( | |
| 346 | member(unit(ExplicitUnit),Infos) -> atom_codes(Atom,ExplicitUnit), ajoin(['"',Atom,'"'],Output), | |
| 347 | Infos2 = [output_pragma('unit',Output)|Infos] ; | |
| 348 | pp(classic,Val,PP) -> ajoin(['"',PP,'"'],AtomPP), | |
| 349 | Infos2 = [output_pragma('inferred_unit',AtomPP)|Infos] ; | |
| 350 | otherwise -> Infos2 = Infos | |
| 351 | ), | |
| 352 | expand_variables(T,T2,Result). | |
| 353 | expand_variables([b(identifier(X),Type,Infos)|T],[b(identifier(X),Type,Infos)|T2],Result) :- | |
| 354 | format('Warning: identifier ~w not in result\n', [X]), | |
| 355 | expand_variables(T,T2,Result), | |
| 356 | '$NOT_COVERED'('This should not happen.'). | |
| 357 | ||
| 358 | set_units_error_context(Context) :- | |
| 359 | retractall(units_error_context(_)), | |
| 360 | assert(units_error_context(Context)). |