-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathadd_sim_records.py
250 lines (205 loc) · 8.24 KB
/
add_sim_records.py
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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
import argparse
import os
import re
import textwrap
from src.db_parser.lexer import Lexer
from src.db_parser.parser import Parser
from src.grouper import Grouper
# Only add SIM fields if type is one of the following:
ALLOWED_SIM_TYPES = [
"ai",
"ao",
"bi",
"bo",
"mbbi",
"mbbo",
"stringin",
"stringout",
"longin",
"longout",
"waveform",
]
def find_macro(name):
if name.find("$") != -1:
left = name.rfind(")") + 1
macro = name[:left]
pvname = name[left:]
return macro, pvname
return None
def get_sim_name(name):
ans = find_macro(name)
if ans is not None:
macro = ans[0]
pvname = ans[1]
# Check for leading :
# e.g. PV name was something like $(P):TEMP rather than $(P)TEMP
if pvname.startswith(":"):
pvname = pvname[1:]
macro += ":"
return macro + "SIM:" + pvname
else:
return "SIM:" + name
def add_waveform_specfic(nelm, ftvl):
string = ""
if nelm is not None or nelm == "":
string += ' field(NELM, "' + nelm + '")\n'
if ftvl is not None or ftvl == "":
string += ' field(FTVL, "' + ftvl + '")\n'
return string
def generate_single_record(original_record, name):
return textwrap.dedent(
"""\
record({},"{}")
{{
field(SCAN, "Passive")
field(DTYP, "Soft Channel"){}
}}
""".format(
original_record.type,
name,
add_waveform_specfic(original_record.get_nelm(), original_record.get_ftvl()),
)
)
def generate_record_text(record, rb, sp, sp_rbv):
string_builder = ""
if rb != "":
rb = get_sim_name(rb)
string_builder += generate_single_record(record, rb)
if sp != "":
sp = get_sim_name(sp)
string_builder += 'alias("' + rb + '","' + sp + '")' + "\n\n"
if sp_rbv != "":
sp_rbv = get_sim_name(sp_rbv)
string_builder += 'alias("' + rb + '","' + sp_rbv + '")' + "\n\n"
elif sp != "":
sp = get_sim_name(sp)
string_builder += generate_single_record(record, sp)
if sp_rbv != "":
sp_rbv = get_sim_name(sp_rbv)
string_builder += 'alias("' + sp + '","' + sp_rbv + '")' + "\n\n"
elif sp_rbv != "":
# Cannot think of any reason why a SP:RBV would exist on its own...
sp_rbv = get_sim_name(sp_rbv)
string_builder += generate_single_record(record, sp_rbv)
return string_builder
def find_common_macro(db):
# Find the most common macro name and whether it is followed by a colon
macros = {}
for r in db.records:
m, pv = find_macro(r.pv)
if m in macros.keys():
macros[m][0] += 1
else:
macros[m] = [0, pv.startswith(":")]
best = None
for m in macros.keys():
if best is None or macros[m][0] > macros[best][0]:
best = m
return best, macros[best][1]
def generate_sim_records(records, sim_record_name, dis_record_name):
sim_prefix = sim_record_name + ":"
grouper = Grouper()
groups = grouper.group_records(records)
output = ""
for g in groups.keys():
sim_record_name = get_sim_name(groups[g].main)
# Check sim record does not already exist - maybe someone
# started writing the records but got bored!
if sim_record_name in records:
continue
# Skip record if it is a simulation record
if groups[g].main.startswith(sim_prefix):
continue
# Skip adding sim record if the original is a soft record
if (
records[groups[g].main].get_dtyp() is None
or records[groups[g].main].get_dtyp().lower() == "soft channel"
):
continue
# No point simulating SIM or DISABLE
if groups[g].RB != sim_record_name and groups[g].RB != dis_record_name:
typ = records[groups[g].main].type
# Don't add simulation record unless the type is suitable
if typ in ALLOWED_SIM_TYPES:
print("ADDED SIM RECORD = " + sim_record_name)
output += generate_record_text(
records[groups[g].main], groups[g].RB, groups[g].SP, groups[g].SP_RBV
)
return output
def generate_modifed_db(
file_in_path, db, file_out_path="generated.db", insert_sims=True, insert_disable=True
):
if db.records is None:
db.records = []
record_names = [record.pv for record in db.records]
records_dict = {name: record for name, record in zip(record_names, db.records)}
record_start_regex = r'record\((\w+),\s*"([\w_\-\:\[\]<>;$\(\)]+)"\)'
with open(file_in_path, "r") as in_file, open(file_out_path, "w") as out_file:
prefix, colon = find_common_macro(db)
if colon:
prefix += ":"
print("COMMON PREFIX = " + prefix)
sim_record_name = prefix + "SIM"
dis_record_name = prefix + "DISABLE"
if insert_sims and sim_record_name not in records_dict:
out_file.write('record(bo, "' + sim_record_name + '")\n')
out_file.write("{\n")
out_file.write(' field(SCAN, "Passive")\n')
out_file.write(' field(DTYP, "Soft Channel")\n')
out_file.write(' field(ZNAM, "NO")\n')
out_file.write(' field(ONAM, "YES")\n')
out_file.write(' field(VAL, "$(RECSIM=0)")\n')
out_file.write("}\n\n")
if insert_disable and dis_record_name not in records_dict:
out_file.write('record(bo, "' + dis_record_name + '") \n')
out_file.write("{\n")
out_file.write(' field(DESC, "Disable comms")\n')
out_file.write(' field(PINI, "YES")\n')
out_file.write(' field(VAL, "$(DISABLE=0)")\n')
out_file.write(' field(OMSL, "supervisory")\n')
out_file.write(' field(ZNAM, "COMMS ENABLED")\n')
out_file.write(' field(ONAM, "COMMS DISABLED")\n')
out_file.write("}\n\n")
curr_record = None
for line in in_file:
matched_pv = re.match(record_start_regex, line)
if matched_pv is not None:
# Found start of record
curr_record = records_dict[matched_pv.groups()[1]]
elif curr_record is not None:
if re.match("}$", line.strip()) is not None:
# Found end, insert sim and dis if necessary
# Only add SIM and SDIS to allowed records which has
# a record type which is not soft channel
if (
curr_record.type in ALLOWED_SIM_TYPES
and curr_record.get_dtyp() is not None
and curr_record.get_dtyp().lower() != "soft channel"
):
if insert_sims and curr_record.get_siml() is None:
name = get_sim_name(curr_record.pv)
out_file.write(' field(SIML, "' + sim_record_name + '")\n')
out_file.write(' field(SIOL, "' + name + '")\n')
if insert_disable and curr_record.get_sdis() is None:
out_file.write(' field(SDIS, "' + dis_record_name + '")\n')
out_file.write(line)
if insert_sims:
new_records = generate_sim_records(records_dict, sim_record_name, dis_record_name)
# If no new records, don't write anything
if new_records.strip() != "":
out_file.write("\n### SIMULATION RECORDS ###\n\n")
out_file.write(new_records)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("file", nargs=1, type=str, help="The base file for adding records to")
parser.add_argument("-o", "--output", nargs=1, default=[], help="The name of the output file")
args = parser.parse_args()
filename = args.file[0]
with open(filename) as file:
if len(args.output) == 1:
out = args.output[0]
else:
f = os.path.split(filename)
out = "sim_" + f[-1]
db_records = Parser(Lexer(file.read())).db()
generate_modifed_db(filename, db_records, out)