1 % (c) 2021-2026 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(simb_parser,[load_simb_file/1,
6 get_default_simb_json_file/1, extended_static_check_default_simb_file/0,
7 simb_activation/3,
8 simb_file_loaded/1, simb_activations_loaded/1,
9 simb_activation_id/1, simb_activation_priority/2, simb_activation_kind/2,
10 simb_activation_effect/6,
11 simb_activation_params/2, simb_activation_options/2,
12 triggers_activation/2]).
13
14
15 :- use_module(probsrc(error_manager)).
16 :- use_module(probsrc(debug)).
17 :- use_module(probsrc(tools_json)).
18 :- use_module(extrasrc(json_parser),[json_parse_file/3]).
19 :- use_module(library(lists)).
20 :- use_module(probsrc(tools),[safe_number_codes/2,ajoin/2]).
21 :- use_module(probsrc(bsyntaxtree), [conjunct_predicates_with_pos_info/3,def_get_texpr_ids/2]).
22 :- use_module(probsrc(tools_lists),[include_maplist/3]).
23
24
25 :- use_module(probsrc(eventhandling),[register_event_listener/3]).
26 :- register_event_listener(reset_specification,reset_simb,'Reset SimB information').
27 :- register_event_listener(reset_prob,reset_simb,'Reset SimB information').
28
29
30 % use_module(extrasrc(simb_simulator)), load_simb_file('RandomWalk_simulation.json')
31 % probsli RandomWalk.mch -simulate RandomWalk_simulation.json 20
32
33
34 :- dynamic simb_file_loaded/1, simb_activation/3, simb_activation_pos/3, simb_activation_effect/6,
35 simb_activation_options/2, simb_activation_params/2.
36
37 simb_activations_loaded(Nr) :- findall(ID,simb_activation_id(ID),L), length(L,Nr).
38
39 simb_activation_id(ID) :- simb_activation(ID,_Prio,_).
40 simb_activation_priority(ID,Prio) :- simb_activation(ID,Prio,_).
41 simb_activation_kind(ID,ActKind) :- simb_activation(ID,_,ActKind).
42 simb_activation_pos(ID,Pos) :- simb_activation_pos(ID,Pos,_ActPos).
43
44 reset_simb :-
45 retractall(simb_file_loaded(_)),
46 retractall(simb_activation(_,_,_)),
47 retractall(simb_activation_pos(_,_,_)),
48 retractall(simb_activation_effect(_,_,_,_,_,_)),
49 retractall(simb_activation_options(_,_)),
50 retractall(simb_activation_params(_,_)).
51
52 :- use_module(probsrc(bmachine),[set_additional_filename_as_parsing_default/3, reset_filename_parsing_default/2]).
53
54 load_simb_file(FileName) :-
55 reset_simb,
56 json_parse_file(FileName,Term,[rest(_),position_infos(true),strings_as_atoms(true)]),
57 add_message(simb,'Parsed JSON: ',FileName),
58 assert(simb_file_loaded(FileName)),
59 set_additional_filename_as_parsing_default(FileName,NewNr,OldNr),
60 call_cleanup(load_simb_json_term(Term,FileName), reset_filename_parsing_default(NewNr,OldNr)).
61
62 load_simb_json_term(json(Objects),FileName) :-
63 %TODO: get_json_key_object(version,)
64 get_json_key_list(activations,Objects,List),
65 %write(List),nl,
66 (maplist(load_activation(FileName),List)
67 -> simb_static_check
68 ; add_warning(simb,'Loading SimB activations failed: ',FileName)
69 ).
70 % TODO: get_json_key_list(listeners,Objects,List),
71
72
73 % load a single activation
74 /* Example activations:
75 {
76 "id": "inc",
77 "execute": "inc",
78 "after": 500,
79 "activating": "move"
80 },
81
82 {
83 "id": "reverse_lift_up_2",
84 "execute": "reverse_lift_up",
85 "activationKind": "single:max",
86 "additionalGuards": "card(call_buttons \\/ inside_buttons) > 0 & min(call_buttons \\/ inside_buttons) > cur_floor",
87 "after": 100,
88 "activating": ["move_up_2",{"id":"other", "params":{"x":"1"}, "probability":"0.2"}]
89 "priority": 3
90 },
91
92 some older SimB files define probabilisticVariables to be uniform instead of transitionSelection attribute
93
94 */
95
96 activation_kinds([multi,single,'single:max','single:min']).
97
98 load_activation(File,json(AttrList)) :-
99 get_optional_json_string_attribute_with_pos(id,File,ID,IPos,AttrList,AttrList0),
100 get_optional_json_strings_attribute_with_pos(params,File,Params,ParamsPos,AttrList0,AttrList1),
101 maplist(create_param_tid(ID,ParamsPos),Params,TParams),
102 activation_kinds(AllowedKinds),
103 get_optional_json_enum_attribute(activationKind,File,AllowedKinds,ActKind,AttrList1,AttrList2),
104 get_optional_json_array_attribute_with_pos(activating,File,ActivatingObjects,APos,AttrList2,AttrList3),
105 include_maplist(get_activating_entry(TParams,File,APos),ActivatingObjects,Activating),
106 % TODO: move activation entries with probabilities to the end or group them
107 get_optional_json_strings_attribute_with_pos(execute,File,OpNames,_EPos,AttrList3,AttrList4),
108 get_optional_json_enum_attribute(transitionSelection,File,[first,uniform],TransSelection,AttrList4,AttrList5),
109 get_optional_json_number_formula_attribute_with_pos(after,File,0,TParams,After,_AftPos,AttrList5,AttrList6),
110 get_optional_json_number_formula_attribute_with_pos(priority,File,1,TParams,Prio,_,AttrList6,AttrList7),
111 get_optional_json_attribute_with_pos(chooseActivation,File,CA,CAPos,AttrList7,AttrList8),
112 findall(triggerActivation(ActivationID,[],Probability),
113 getChooseActivationEntry(CA,File,TParams,ActivationID,Probability),CAList),
114 (CAList \= [],
115 add_message(simb,'chooseActivation deprecated, use probability in activating:','',CAPos),
116 get_probability_sum(CAList,0,ProbSum), Epsilon = 0.0001,
117 (ProbSum < 1.0-Epsilon ; ProbSum > 1.0 + Epsilon)
118 -> add_message(simb,'Probabilities do not add up to 1.0: ',ProbSum,CAPos)
119 ; true
120 ),
121 (CAList = [] -> ActivationList = Activating
122 ; Activating = [] -> ActivationList=CAList
123 ; add_message(simb,'Combining activating and chooseActivation attributes: ',Activating,APos),
124 append(Activating,CAList,ActivationList) % may be problematic if both use probabilties
125 ),
126 (get_optional_json_string_attribute_with_pos(predicate,File,GuardS,GPos,AttrList8,AttrList9),
127 GuardS \= @(null) -> true
128 ; get_optional_json_string_attribute_with_pos(additionalGuards,File,GuardS,GPos,AttrList8,AttrList9)
129 -> (GuardS = @(null) -> true
130 ; add_message(simb,'additionalGuards is deprecated; use predicate instead','',GPos)
131 )
132 ),
133 % predicate would be a better name than additionalGuards
134 (GuardS = @(null) -> AddGuard0 = b(truth,pred,[])
135 ; parse_pred_for_ops(OpNames,TParams,GuardS,AddGuard0,GPos) -> true
136 ; add_warning(simb,'additionalGuards cannot be parsed, discarding: ',GuardS,GPos),
137 AddGuard0 = b(truth,pred,[])
138 ),
139 maplist(check_param_type(ID,ParamsPos),TParams),
140 get_optional_json_bool_attr(activatingOnlyWhenExecuted,true,File,[],Opts1,AttrList9,AttrList10),
141 get_optional_json_bool_attr(errorWhenNotExecuted,false,File,Opts1,Options,AttrList10,AttrList11),
142 get_optional_json_attribute_with_pos(fixedVariables,File,FixV,FixPos,AttrList11,AttrList12),
143 findall('='(Var,Val),getFixedVariablesEntry(FixV,File,Var,Val),EqList),
144 (EqList = [] -> AddGuard2 = AddGuard0
145 ; OpNames \= [_]
146 -> add_warning(simb,'fixedVariables can only be used for a single operation in execute: ',OpNames,FixPos),
147 AddGuard2 = AddGuard0
148 ; OpNames = [OpName], GP=gen_parse_errors_for(FixPos),
149 (tcltk_interface:parse_tclk_parameter_values_and_pred(OpName,EqList,'btrue',AddGuard1,GP) %TODO: move to module
150 % TODO: make TParams available
151 -> conjunct_predicates_with_pos_info(AddGuard0,AddGuard1,AddGuard2)
152 ; add_warning(simb,'fixedVariables cannot be parsed, discarding: ',EqList,FixPos),
153 AddGuard2 = AddGuard0
154 )
155 ),
156 (get_json_attribute_with_pos(Attr,AttrList12,File,_,Pos),
157 nonmember(Attr,[comment]),
158 add_warning(simb,'Unknown/unsupported SimB attribute: ',Attr,Pos),
159 fail ; true),
160 assert_simb_activation(ID,TParams,Prio,ActKind,OpNames,AddGuard2,TransSelection,After,
161 ActivationList,Options,IPos,APos).
162
163 get_optional_json_enum_attribute(Attr,File,AllowedChoices,Res,ObjList,RemObjList) :-
164 get_optional_json_string_attribute_with_pos(Attr,File,Value,VPos,ObjList,RemObjList),
165 (Value = @(null) -> AllowedChoices = [Res|_] % take first choice as default
166 ; member(Value,AllowedChoices) -> Res=Value
167 ; add_warning(simb,'Illegal value for attribute: ',Value,VPos),
168 AllowedChoices = [Res|_]
169 ).
170
171 % parse predicate for given 'execute' list of operations
172 parse_pred_for_ops(_,_ActivationParams,'',AddGuard,_GPos) :- !,
173 AddGuard = b(truth,pred,[]).
174 parse_pred_for_ops([OpName],ActivationParams,GuardS,AddGuard,GPos) :- !,
175 % we have a single operation and can make parameters of the operation available
176 parse_pred_for_op(GuardS,OpName,ActivationParams,AddGuard,GPos).
177 parse_pred_for_ops(_,ActivationParams,GuardS,AddGuard,_GPos) :-
178 % multiple operations, only allow using variables and params
179 get_param_extra_scope(ActivationParams,ExtraScope),
180 b_parse_machine_predicate(GuardS,[variables|ExtraScope],AddGuard).
181
182 :- use_module(probsrc(bmachine), [b_parse_machine_operation_pre_post_predicate/5,
183 b_parse_machine_predicate/3,
184 b_parse_machine_expression_from_codes_with_prob_ids/4]).
185
186 % get optional boolean attribute and if true then add to options list:
187 get_optional_json_bool_attr(Attr,Default,File,InOpts,OutOpts,ObjList,RemObjList) :-
188 get_optional_json_string_attribute_with_pos(Attr,File,Val,Pos,ObjList,RemObjList),
189 (Val=true -> OutOpts = [Attr|InOpts]
190 ; Val = @(null), Default=true -> OutOpts = [Attr|InOpts]
191 ; OutOpts = InOpts,
192 (member(Val,[false,true,@(null)]) -> true
193 ; add_warning(simb,'Illegal value for boolean attribute: ',Val,Pos))
194 ).
195
196 get_optional_json_number_formula_attribute_with_pos(Attr,File,Default,TActivationParams,
197 NrValue,Pos,ObjList,RemObjList) :-
198 get_optional_json_attribute_with_pos(Attr,File,Value,Pos,ObjList,RemObjList),
199 extract_json_number_or_formula(Value,Attr,Pos,Default,TActivationParams,NrValue).
200
201 :- use_module(probsrc(bsyntaxtree),[get_texpr_type/2]).
202 % extract_json_number_or_formula:
203 % allows both JSON numbers and strings which are parsed as a B formula
204 % if Default is none we also allow non-integer/real formulas
205 extract_json_number_or_formula(number(S),_,_,_Default,_,Res) :- !, Res=S.
206 extract_json_number_or_formula(@(null),_,_,Default,_,Res) :- Default \= no_default, !, Res = Default.
207 extract_json_number_or_formula(string(S),Attr,Pos,Default,TActivationParams,Res) :-
208 atom_codes(S,Codes),
209 GenParseErrors=gen_parse_errors_for(Pos),
210 get_param_extra_scope(TActivationParams,ExtraScope),
211 b_parse_machine_expression_from_codes_with_prob_ids(Codes,ExtraScope,TypedExpr,GenParseErrors),
212 get_texpr_type(TypedExpr,Type),
213 !,
214 (extract_number_literal(TypedExpr,ENr) -> Res=ENr
215 ; Type = integer -> Res=TypedExpr
216 ; Type = real -> Res=TypedExpr
217 ; Type = any -> Res=TypedExpr,
218 add_message(simb,'Type any for attribute: ',Attr) % probably due to params; try and fix
219 ; \+ number(Default) -> Res=TypedExpr
220 ; ajoin(['Type for attribute ',Attr,' must be integer or real but is:'],Msg),
221 add_error(simb,Msg,Type,Pos),
222 Res=Default
223 ).
224 extract_json_number_or_formula(Obj,Attr,Pos,Default,_,Res) :-
225 add_error(simb,'Illegal JSON number / formula value for attribute: ',Attr,Pos),
226 write(Obj),nl,
227 Res=Default.
228
229
230 :- use_module(probsrc(kernel_reals),[construct_real_number/2]).
231 % convert if possible a static expression into a Prolog value; we could try and call b_compiler?
232 % see evaluable_integer_expression and compute_static_int_texpression/ compute_static_expression
233 extract_number_literal(b(integer(Nr),integer,_),Nr).
234 extract_number_literal(b(real(R),real,_),Nr) :- construct_real_number(R,Nr).
235
236 get_param_extra_scope(TActivationParams,ExtraScope) :-
237 (TActivationParams=[] -> ExtraScope = [] ; ExtraScope = [identifier(TActivationParams)]).
238
239 parse_pred_for_op(Pred,OpName,TActivationParams,TypedPred,Pos) :-
240 get_param_extra_scope(TActivationParams,ExtraScope),
241 b_parse_machine_operation_pre_post_predicate(Pred,ExtraScope,TypedPred,OpName,gen_parse_errors_for(Pos)).
242
243 % crate a typed id for passing as ExtraScope for parsing
244 create_param_tid(ActID,ParamsPos,ID,b(identifier(ID),_ANYTYPE,[activation_param(ActID),nodeid(ParamsPos)])).
245
246 check_param_type(ActID,ParamsPos,b(identifier(ID),TYPE,_)) :-
247 (ground(TYPE) -> true
248 ; ajoin(['Type of parameter ',ID,' could not be determined from predicate for: '],Msg),
249 add_warning(simb,Msg,ActID,ParamsPos)
250 ). % TODO: instantiate
251 :- use_module(probsrc(bmachine),[b_top_level_operation/1]).
252 :- use_module(probsrc(tools_matching), [get_possible_operation_matches_msg/2]).
253
254 special_operation('$setup_constants').
255 special_operation('$initialise_machine').
256 special_operation(skip).
257
258 assert_simb_activation(ID,_,Prio,ActKind,OpNames,AddGuard,TransSelection,After,ActivationList,Options,IPos,_) :-
259 (debug_mode(off) -> true
260 ; format('Loaded Activation id=~w~n Priority: ~w~n Activation Kind: ~w~n Execute: ~w (~w)~n Guard: ',[ID,Prio,ActKind,OpNames,TransSelection]),
261 translate:print_bexpr(AddGuard),nl,
262 format(' After: ~w ms~n Activating: ~w~n Options: ~w~n~n',[After,ActivationList,Options])
263 ),
264 member(OpName,OpNames),
265 \+ special_operation(OpName),
266 \+ b_top_level_operation(OpName),
267 (get_possible_operation_matches_msg(OpName,FMsg)
268 -> ajoin(['Unknown operation in SimB Activation ',ID,' (did you mean ',FMsg,' ?) :'], Msg),
269 add_warning(simb,Msg,OpName,IPos)
270 ; add_warning(simb,'Unknown top-level operation: ',OpName,IPos)
271 ),
272 fail.
273 assert_simb_activation(ID,_,_Prio,_,_Execute,_,_,_After,_ActivationList,_,IPos,_) :-
274 simb_activation_id(ID),
275 add_warning(simb,'Duplicate definition of SimB Activation: ',ID,IPos),
276 fail.
277 assert_simb_activation(ID,Params,Prio,ActKind,Execute,AddGuard,TransSelection,After,ActivationList,Options,IPos,APos) :-
278 assert(simb_activation(ID,Prio,ActKind)),
279 assert(simb_activation_pos(ID,IPos,APos)),
280 assert(simb_activation_effect(ID,Execute,AddGuard,TransSelection,After,ActivationList)),
281 assert(simb_activation_params(ID,Params)),
282 assert(simb_activation_options(ID,Options)).
283
284 %:- use_module(probsrc(bsyntaxtree), [get_texpr_pos/2]).
285 %get_activation_params_pos(ID,Pos) :-
286 % simb_activation_params(ID,[TID|_]),
287 % get_texpr_pos(TID,Pos).
288 %get_activation_params_pos(_,unknown).
289
290 % extract entries from RHS of something like: "chooseActivation": {"inc": "0.8", "dec": "1"}
291 getChooseActivationEntry(json(CAL),File,TActivationParams,ActivationID,Probability) :-
292 get_json_attribute_with_pos(ActivationID,CAL,File,PVal,Pos),
293 extract_json_number_or_formula(PVal,probability,Pos,1,TActivationParams,Probability).
294
295 atom_to_nr(Atom,Nr) :- atom_codes(Atom,CC), safe_number_codes(Nr,CC).
296
297 get_probability_sum([],Acc,Acc).
298 get_probability_sum([triggerActivation(_,_,Probability)|T],Acc,Res) :- number(Probability), % fails for formulas
299 Acc2 is Acc+Probability,
300 get_probability_sum(T,Acc2,Res).
301
302 % extract a single activating entry: either activation id as string, or an object with id, params, ... attributes
303 get_activating_entry(_,_,_,string(ActivationID),Res) :- !, % single activation id without parameter or probability
304 Res = ActivationID.
305 get_activating_entry(TParams,File,Pos,json(AttrList),Res) :- !,
306 get_optional_json_string_attribute_with_pos(id,File,ActID,_IPos,AttrList,AttrList0),
307 (ActID = @(null)
308 -> add_error(simb,'Entries for activating need an id field:',AttrList,Pos), fail
309 ; true
310 ),
311 get_optional_json_attribute_with_pos('params',File,ParaObj,_,AttrList0,AttrList1),
312 findall(param(ParameterName,ParameterValue),
313 getParameterValueEntry(ParaObj,File,TParams,ParameterName,ParameterValue),ParaList),
314 (get_json_attribute_with_pos('probability',AttrList1,File,PVal,PPos)
315 -> extract_json_number_or_formula(PVal,probability,PPos,1,TParams,Probability)
316 ; Probability=none
317 ),
318 Res = triggerActivation(ActID,ParaList,Probability).
319 get_activating_entry(_,_,Pos,Obj,_Res) :-
320 add_error(simb,'Unknown activating entry:',Obj,Pos),fail.
321
322
323 % extract ParameterName/Value pairs from a JSON object for params attribute:
324 % example: "activating" : [{"id":"ENV_Turn_EngineOn_1","params":{"x":"1","y":"TRUE"}}]
325 getParameterValueEntry(json(CAL),File,TActivationParams,ParameterName,ParameterValue) :-
326 get_json_attribute_with_pos(ParameterName,CAL,File,PVal,Pos),
327 extract_json_number_or_formula(PVal,parameter_value,Pos,no_default,TActivationParams,ParameterValue).
328
329 % choose entries from RHS of something like: "fixedVariables": {"delta": "curDeadlines(blink_deadline) - curTime"},
330 getFixedVariablesEntry(json(FV),File,VariableParameterName,Value) :-
331 get_json_attribute_with_pos(VariableParameterName,FV,File,PVal,Pos),
332 extract_json_string(PVal,fixedVariables,Pos,Value).
333
334 % --------------------
335
336 :- use_module(probsrc(pref_definitions),[b_get_definition_string_from_spec/3]).
337 :- use_module(probsrc(bmachine),[bmachine_is_precompiled/0, b_absolute_file_name_relative_to_main_machine/2]).
338 get_default_simb_json_file(FullPath) :- bmachine_is_precompiled,
339 b_get_definition_string_from_spec('SIMB_JSON_FILE', _Pos, Path),
340 b_absolute_file_name_relative_to_main_machine(Path,FullPath).
341
342 extended_static_check_default_simb_file :- simb_file_loaded(_),!, % a file was already loaded
343 simb_static_check.
344 extended_static_check_default_simb_file :-
345 (get_default_simb_json_file(SimBFile)
346 -> load_simb_file(SimBFile) % will perform static check
347 ; true). % nothing to check
348 % --------------------
349
350 % linting / static checking
351
352 :- dynamic called_activation_id/1.
353
354
355 initial_activation('$setup_constants').
356 initial_activation('$initialise_machine').
357
358 :- use_module(library(ordsets),[ord_subtract/3]).
359 :- use_module(probsrc(bmachine),[b_machine_has_constants_or_properties/0]).
360 simb_static_check :- ID = '$initialise_machine',
361 simb_activation_pos(ID,Pos,_),
362 simb_activation_effect(ID,Execute,_,_,_,_), Execute = [ID],
363 \+ simb_activation_id('$setup_constants'),
364 b_machine_has_constants_or_properties,
365 OpNames = ['$setup_constants'], Guard = b(truth,pred,[]),
366 ActivationList = [ID], P = unknown,
367 add_message(simb,'Adding $setup_constants activation to activate: ',ID,Pos),
368 assert_simb_activation('$setup_constants',[],1,multi,OpNames,Guard,first,0,ActivationList,[],P,P),
369 fail.
370 simb_static_check :-
371 retractall(called_activation_id(_)),
372 simb_activation_pos(ID,_Pos,ActPos),
373 simb_activation_effect(ID,_Execute,_AddGuard,_TransSelection,_After,ActivationList),
374 triggers_activation(ActivationList,ID2,CallParams),
375 (called_activation_id(ID2) -> true
376 ; assert(called_activation_id(ID2))
377 ),
378 (simb_activation_id(ID2)
379 -> simb_activation_params(ID2,TParamIDs),
380 def_get_texpr_ids(TParamIDs,ParamIds), sort(ParamIds,SParamIds),
381 maplist(get_param_id,CallParams,CallParamIds), sort(CallParamIds,SCallParamIds),
382 (ord_subtract(SCallParamIds,SParamIds,UnknownParas),
383 UnknownParas = [_|_],
384 ajoin(['Activation ',ID,' declares unknown parameters when activating ',ID2,': '],Msg),
385 add_warning(simb,Msg,UnknownParas,ActPos)
386 ;
387 ord_subtract(SParamIds,SCallParamIds,ParasNotSet),
388 ParasNotSet = [_|_],
389 ajoin(['Activation ',ID,' does not define parameters when activating ',ID2,': '],Msg),
390 add_warning(simb,Msg,ParasNotSet,ActPos)
391 )
392 ; ajoin(['Activation ',ID,' triggers an unknown activation: '],Msg),
393 add_warning(simb,Msg,ID2,ActPos)
394 ),
395 fail.
396 simb_static_check :-
397 simb_activation_pos(ID,Pos,_),
398 \+ called_activation_id(ID),
399 \+ initial_activation(ID),
400 add_message(simb,'Activation is not triggered by any other activation: ',ID,Pos),
401 fail.
402 simb_static_check.
403
404 get_param_id(param(ID,_),ID).
405
406 triggers_activation(ActivationList,ID) :- triggers_activation(ActivationList,ID,_Params).
407
408 triggers_activation(ActivationList,ID,Params) :-
409 member(Act,ActivationList),
410 (atomic(Act) -> ID=Act, Params = []
411 ; Act = triggerActivation(AID,Params,_Prob) -> ID=AID).