-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathordering.jl
229 lines (202 loc) · 7.84 KB
/
ordering.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# Check the ordering of items in a DDLm dictionary matches the order
# prescribed by the style guide.
get_atts(a) = begin
cat = String(a.first)
atts = map(x->"_"*cat*"."*"$x",String.(a.second))
end
get_att_order(a) = begin
order = []
for cat in a
append!(order,get_atts(cat))
end
return order
end
const atts_as_strings = get_att_order(ddlm_attribute_order)
const toplevel_strings = get_att_order(ddlm_toplevel_order)
mutable struct OrderCheck <: Visitor_Recursive
all_defs::Array{String,1} #all definitions in file
seen_items::Array{String,1} #attributes seen in current definition
seen_defs::Array{String,1} #definitions seen so far
top_level::Array{String,1}
cat_info::Array{Tuple{String,String},1}
this_def::String
this_parent::String
func_cat::String
is_su::Bool
linked::String
origin_dir::AbstractPath #for imports
warn::Bool #emit warnings
end
OrderCheck() = OrderCheck(@__DIR__,false)
OrderCheck(s::String,w::Bool,all_defs::Vector{String}) = OrderCheck(all_defs,[],[],[],[],"","","",false,"",Path(s),w)
@rule scalar_item(oc::OrderCheck,tree) = begin
att = traverse_to_value(tree.children[1],firstok=true)
val = traverse_to_value(tree.children[2],firstok=true)
push!(oc.seen_items,att)
check_attribute(oc,att,val,tree)
end
@rule loop(oc::OrderCheck,tree) = begin
boundary = findfirst(x-> !isa(x,Lerche.Token),tree.children)
append!(oc.seen_items,tree.children[2:boundary-1])
end
@rule save_frame(oc::OrderCheck,tree) = begin
check_order(atts_as_strings,oc.seen_items,"4.3.4",get_line(tree),warn=oc.warn)
cats = map(x->to_cat_obj(x)[1],oc.seen_items)
if "import_details" in cats
print_err(get_line(tree),"No import_details attributes should be used",err_code="4.3.3")
end
if !(oc.this_parent in oc.seen_defs) &&
length(oc.seen_defs) > 0 && #Head is first
oc.this_parent in oc.all_defs
print_err(get_line(tree),"Definition for child item $(oc.this_def) comes before category $(oc.this_parent)",err_code="4.1.8")
@debug "Seen $(oc.seen_defs)" oc.all_defs
end
# Check SU is straight after parent
if oc.is_su && oc.seen_defs[end] != oc.linked
print_err(get_line(tree),"SU $(oc.this_def) does not immediately follow its Measurand item $(oc.linked)",err_code="4.1.10")
end
if oc.this_def != ""
push!(oc.seen_defs, oc.this_def)
# If parent == def, we have the head category and can skip it
if oc.this_def != oc.this_parent
push!(oc.cat_info,(oc.this_def,oc.this_parent))
end
end
# Make sure a category definition occurs before any data names/children from that category
if occursin(".", oc.this_def)
previous_cat = findlast(x -> !occursin(".",x),oc.seen_defs)
if lowercase(oc.seen_defs[previous_cat]) != oc.this_parent &&
oc.this_parent in oc.all_defs
print_err(get_line(tree),"Definition for data name $(oc.this_def) is not grouped after parent category $(oc.this_parent)",err_code="4.1.8")
end
end
# Pretend we never saw any SU values to save order checking later
if oc.this_def != "" && oc.is_su
pop!(oc.seen_defs)
pop!(oc.cat_info)
end
oc.this_def = ""
oc.is_su = false
oc.linked = ""
end
@rule block_content(oc::OrderCheck,tree) = begin
if tree.children[1].data == "data"
append!(oc.top_level,oc.seen_items)
end
oc.seen_items = []
end
@rule dblock(oc::OrderCheck,tree) = begin
check_order(toplevel_strings,oc.top_level,"4.2.1",get_line(tree))
# check that looped items are after save frames
saves_seen = false
for b in tree.children[2]
if b.data == "save_frame"
saves_seen = true
continue
end
if b.data == "data" && saves_seen
if b.children[2].children[1].data != "loop"
print_err(get_line(b.children[2].children[1]),"Non-looped top level items appear after save frames",err_code="4.2.2")
end
end
end
check_def_order(oc.cat_info,oc.func_cat)
end
# Collect a list of all cats so we only flag bad ordering when cats are present
mutable struct CatCollector <: Visitor_Recursive
all_defs::Array{String,1}
end
CatCollector() = CatCollector([])
@rule scalar_item(cc::CatCollector,tree) = begin
att = traverse_to_value(tree.children[1],firstok=true)
val = traverse_to_value(tree.children[2],firstok=true)
if att == "_definition.id"
push!(cc.all_defs, lowercase(val))
end
end
to_cat_obj(v) = begin
c,o = split(v,".")
return lowercase(c[2:end]),lowercase(o)
end
check_order(right,observed,err_code,line;warn=false) = begin
known = filter(x->x in right,observed)
checked = filter(x->x in observed,right)
if warn
unknown = setdiff(observed,right)
if length(unknown) > 0
println("WARNING: unknown attributes $unknown")
end
end
if known != checked
print_err(line,"Order of items in definition incorrect:",err_code=err_code)
@printf "\n%-30s%-30s\n" "Actual" "Expected"
for i in 1:minimum(length,[known,checked])
@printf "%-30s%-30s\n" known[i] checked[i]
end
println()
end
end
check_def_order(cat_tuples,func_cat) = begin
def_order = lowercase.([x[1] for x in cat_tuples])
all_cats = unique!(filter(x-> !occursin(".",x),def_order))
for one_cat in all_cats
children = filter(x->x[2] == one_cat && x[1] in all_cats && x[1] != func_cat,cat_tuples)
children = [x[1] for x in children]
if sort(children) != children
print_err(0,"Child categories of category $one_cat not in alphabetical order", err_code="4.1.9")
for (s,c) in zip(sort(children),children)
@printf "%-30s%-30s\n" s c
end
end
children = filter(x->x[2] == one_cat && !(x[1] in all_cats),cat_tuples)
children = [x[1] for x in children]
if sort(children) != children
print_err(0,"Child data names of category $one_cat not in alphabetical order", err_code="4.1.8")
for (s,c) in zip(sort(children),children)
@printf "%-30s%-30s\n" s c
end
end
end
if func_cat != "" && all_cats[end] != func_cat
print_err(0,"Function category is not the final category",err_code="4.1.11")
end
end
# Extract necessary information from imported contents
process_import(oc::OrderCheck,val,tree) = begin
if get(val,"mode","Contents") == "Full" return end
templ_file_name = joinpath(oc.origin_dir, val["file"])
templ_file = nothing
try
templ_file = Cif(templ_file_name,native=true)
catch
@warn "unable to resolve $templ_file_name to a filename"
return
end
templates = get_frames(first(templ_file)[end])
target_block = templates[val["save"]]
# Now check for items we care about
for (a,v) in target_block
if length(v) > 1 continue end #we only care about single-valued items
check_attribute(oc,a,v[],tree)
end
end
# Placed here so it can be called from import routine and
# from main routine
check_attribute(oc::OrderCheck,att,val,tree) = begin
if att == "_definition.id"
oc.this_def = lowercase(val)
elseif att == "_name.category_id"
oc.this_parent = lowercase(val)
elseif att == "_definition.class"
if val == "Head" && length(oc.seen_defs) != 0
print_err(get_line(tree),"Head category should be the first definition",err_code="4.1.7")
end
if val == "Functions" oc.func_cat = oc.this_def end #assume comes after
elseif att == "_name.linked_item_id"
oc.linked = lowercase(val)
elseif att == "_type.purpose"
oc.is_su = val == "SU"
elseif att == "_import.get"
process_import(oc,read_import_spec(tree.children[2]),tree)
end
end