1 % Heinrich Heine Universitaet Duesseldorf
2 % (c) 2021-2026 Lehrstuhl fuer Softwaretechnik und Programmiersprachen,
3 % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html)
4
5 :- module(visb_json_utils,[get_attr/3, get_attr_with_pos/5,
6 del_attr/4, del_attr_with_pos/6, non_det_del_attr_with_pos/6,
7 force_del_attr/5, force_del_attr_with_pos/6,
8 get_attr_true/3, is_optional_json_item/1, is_optional_json_item/3,
9 json_true_value/1, json_false_value/1,
10 check_json_string/4,
11 get_pos_from_list/3, construct_prob_pos_term/3,
12 replace_in_json/4, multi_replace_in_json/4,
13 check_all_same_length_json/3]).
14
15 :- use_module(probsrc(module_information),[module_info/2]).
16 :- module_info(group,visualization).
17 :- module_info(description,'This module provides utilities to access and transform JSON terms for VisB.').
18
19 % Note: error messages use visb_visualiser as source, as they relate to VisB JSON files
20
21 :- use_module(probsrc(error_manager)).
22 :- use_module(probsrc(self_check)).
23 :- use_module(probsrc(tools_strings), [ajoin/2]).
24 :- use_module(library(lists)).
25
26 % JSON terms as produced by the JSON parser with position_infos(true):
27 % json(List) where List contains '='(Attr,Value,From-To) entries
28
29 % ----------------
30 % accessing attributes
31
32 get_attr(Attr,List,Val) :- member('='(Attr,Val,_),List).
33 get_attr_with_pos(Attr,List,Val,File,Pos) :-
34 El = '='(Attr,Val,_),
35 member(El,List),
36 get_pos_from_list([El],File,Pos).
37
38 del_attr(Attr,List,Val,Rest) :- select('='(Attr,Val,_Pos),List,Rest).
39 %del_attr_with_pos(Attr,List,Val,Rest,Pos) :- select('='(Attr,Val,Pos),List,Rest).
40 del_attr_with_pos(Attr,List,Val,Rest,File,PosTerm) :- select('='(Attr,Val,From-To),List,Rest),!,
41 PosTerm = src_position_with_filename_and_ec(From,1,To,1,File).
42 non_det_del_attr_with_pos(Attr,List,Val,Rest,File,PosTerm) :- select('='(Attr,Val,From-To),List,Rest),
43 PosTerm = src_position_with_filename_and_ec(From,1,To,1,File).
44
45 force_del_attr(Attr,List,Val,Rest,File) :- force_del_attr_with_pos(Attr,List,Val,Rest,File,_).
46
47 force_del_attr_with_pos(Attr,List,Val,Rest,File,PosTerm) :- select('='(Attr,Val,From-To),List,Rest),!,
48 %TODO: we would ideally want to extract the exact column position of Val, for this we need to extend the JSON parser first
49 PosTerm = src_position_with_filename_and_ec(From,1,To,1,File).
50 force_del_attr_with_pos(Attr,List,_,_,File,_) :- get_pos_from_list(List,File,Pos),!,
51 add_error(visb_visualiser,'The JSON object has no attribute:',Attr,Pos),fail.
52
53 construct_prob_pos_term(From-To,File,PosTerm) :- !,
54 PosTerm = src_position_with_filename_and_ec(From,1,To,1,File).
55 construct_prob_pos_term(Pos,_,Pos).
56
57 get_pos_from_list(json(List),File,Pos) :- !, get_pos_from_list(List,File,Pos).
58 get_pos_from_list([H|T],File,RPos) :-
59 H='='(_,_,From-_),
60 last([H|T],
61 '='(_,_,_-To)),
62 !,
63 RPos=src_position_with_filename_and_ec(From,1,To,1,File).
64 get_pos_from_list(_,File,File). % should we provide another term?
65
66 check_json_string(Term,_,_,Atom) :- Term = string(Atom), atom(Atom), !.
67 check_json_string(Term,Attr,Pos,_) :- !,
68 ajoin(['Illegal JSON attribute ',Attr,', expected a JSON string:'],Msg),
69 add_error(visb_visualiser,Msg,Term,Pos),fail.
70
71 % ----------------
72 % JSON boolean values and flags
73
74 get_attr_true(Attr,JsonList,File) :-
75 get_attr_with_pos(Attr,JsonList,TRUE,File,Pos),
76 (json_true_value(TRUE) -> true
77 ; json_false_value(TRUE) -> fail
78 ; ajoin(['Value for attribute ',Attr,' is not true or false: '],Msg),
79 add_warning(visb_visualiser,Msg,TRUE,Pos)
80 ).
81
82 % check if a JSON item has been marked as optional, e.g., "optional":true
83 is_optional_json_item(JsonList) :-
84 get_attr(optional,JsonList,Val),
85 \+ json_false_value(Val). % optional not marked as false
86 is_optional_json_item(JsonList,File,Pos) :-
87 get_attr_with_pos(optional,JsonList,Val,File,Pos), \+ json_false_value(Val).
88
89 json_true_value(@(true)).
90 json_true_value(string(true)).
91 json_true_value(number(1)).
92
93 json_false_value(@(false)).
94 json_false_value(string(false)).
95 json_false_value(number(0)).
96
97 % ----------------
98 % replacing %Nr patterns, used for for-loops and repeat in VisB JSON files
99
100 check_all_same_length_json(List,ExpectedLen,Pos) :-
101 nth1(Nr,List,Json),
102 (Json=array(SubList),length(SubList,Len) -> Len \= ExpectedLen ; Len='not a list'),
103 !,
104 ajoin(['The element number ',Nr,' of the repeat list has not the expected length of ',ExpectedLen,', length is: '],Msg),
105 add_warning(visb_visualiser,Msg,Len,Pos).
106 check_all_same_length_json(_,_,_).
107
108 json_value_to_codes(@(Literal), C) :- atom(Literal), !, atom_codes(Literal, C).
109 json_value_to_codes(number(Number), C) :- number(Number), !, number_codes(Number, C).
110 json_value_to_codes(string(String), C) :- atom(String), !, atom_codes(String, C).
111 json_value_to_codes(Json, _) :- !, add_error(json_value_to_codes,'Cannot convert json term to codes:', Json), fail.
112
113 % replace (possibly) multiple patterns at once
114 multi_replace_in_json(array([]),_RepCount,JsonIn,JsonOut) :- !, JsonOut=JsonIn.
115 multi_replace_in_json(array([IterElem|TIT]),RepCount,JsonIn,JsonOut) :-
116 !,
117 number_codes(RepCount,Pat),
118 json_value_to_codes(IterElem,IterElemC),
119 %format('Replacing in JSON: ~s -> ~s~n',[Pat,IterElemC]),
120 replace_in_json(Pat,IterElemC,JsonIn,Json),
121 R1 is RepCount+1,
122 multi_replace_in_json(array(TIT),R1,Json,JsonOut).
123 multi_replace_in_json(IterElem,RepCount,JsonIn,JsonOut) :- % a value, not a list
124 !,
125 number_codes(RepCount,Pat),
126 json_value_to_codes(IterElem,IterElemC),
127 %format('Replacing in JSON: ~s -> ~s~n',[Pat,IterElemC]),
128 replace_in_json(Pat,IterElemC,JsonIn,JsonOut).
129
130 % replace a pattern code prefixed by % by another code list
131 replace_in_json(Pattern,RepStr,string(Val),string(NewVal)) :- !, replace_atom(Pattern,RepStr,Val,NewVal).
132 replace_in_json(Pattern,RepStr,array(Vals),array(NewVals)) :- !, maplist(replace_in_json(Pattern,RepStr),Vals,NewVals).
133 replace_in_json(Pattern,RepStr,json(Vals),json(NewVals)) :- !, maplist(replace_in_json_pair(Pattern,RepStr),Vals,NewVals).
134 replace_in_json(_,_,Val,Val). % number/literal (no replacement necessary)
135
136 replace_in_json_pair(Pattern,RepStr,'='(KIn,VIn,Pos),'='(KOut,VOut,Pos)) :-
137 replace_atom(Pattern,RepStr,KIn,KOut),
138 replace_in_json(Pattern,RepStr,VIn,VOut).
139
140 replace_atom(Pattern,RepStr,Val,NewVal) :-
141 atom_codes(Val,Codes),
142 replace_pat(Pattern,RepStr,NewCodes,Codes,[]),
143 atom_codes(NewVal,NewCodes). % TO DO: keep as codes for efficiency
144
145 :- assert_must_succeed((visb_json_utils: replace_pat("0","_1_",Res,"ab%0cd",[]), Res == "ab_1_cd")).
146 :- assert_must_succeed((visb_json_utils: replace_pat("0","",Res,"ab%0%0cd%0",[]), Res == "abcd")).
147 :- assert_must_succeed((visb_json_utils: replace_pat("1","x",Res,"%1 %10 %1",[]), Res == "x %10 x")).
148 % dcg utility to replace %Pat by NewStr constructing Res
149 replace_pat(Pat,NewStr,Res) --> "%", Pat, \+ digit_follows_number(Pat), !,
150 {append(NewStr,TR,Res)}, replace_pat(Pat,NewStr,TR).
151 replace_pat(Pat,RepStr,[H|T]) --> [H],!, replace_pat(Pat,RepStr,T).
152 replace_pat(_,_,[]) --> [].
153
154 % check that a numeric pattern like %1 is not the prefix of a longer one like %10
155 digit_follows_number(Pat,S,S) :- last(Pat,C), is_digit_code(C), S=[D|_], is_digit_code(D).
156 is_digit_code(C) :- C >= 0'0, C =< 0'9.