Dhanya Thattil ce7270e8a2
commands code generation (#871)
* commands code generation  (#803)

* commands code generation for only frames command

* fix cmake file and add Caller files

* working exptime, fully extended commands file and its variants

* start adding template commands

* add INT_CMD_VEC_ID template

* add list command, generate multiple bins, format code

* reach 208 commands using the cpp macros

* add tests for command parser

* start adding tests for commands parser

* fix typo to use commands.yaml

* add more tests for command_parser

* add all template functions (up to 218 commands)

* finish template functions and add more CmdProxy.cpp functions (250+)

* 257 commands

* 300 commands the rest are very special commands

* add special commands without generation

* separate special functions from generated c++ file

* implementing one command for put and get (buggy)

* add infer action in a separate file

* generate header for special commands from yaml

* allow only 0 or 1 for bool inputs

* group all commands in gen_commands.py

* add help to gen_commands.py

* add autocomplete bash script

* autocompletion: add support for module levels and help

* remove debugging line

* add autocompletion, help to commands, change int [0,1] to bool

* copy tests for Caller.cpp. Tests pass

* update with the new developer branch changes

* fix errors after merging (there is problems with tests)

* fixed port/stopport in yaml (intput typo), added '_caller' to the test dac and test on chip dac command in global test for cmdcaller

* undo previous test simulator debug change

* add documentation for the generated code

* reducing the comment to be replaced in length so formatting does not split into 2 lines

* removed formatting specific style of C++11 in gen_commands.py to keep with the top level clang format of the project
* regeneratign code for commands

* automation generated

* Redirect deprecated commands (#872)

* working implementation, need to fix dac

* fixed deprecation redirect for dac command

* Detector specific autocomplete (#873)

* working implementation, need to fix dac

* fixed deprecation redirect for dac command

* detector specific completion for dac

* added autocomplete using detector specific

* fixed error when autocompleting partial words

* Generate commands/fix commands (#875)

* fix vm_a, im_a etc have deg Celsius suffix, also help missing or changed in some places

* dac: require det id for all, arg0 to be printed at output, help for onchip dac and dac, onchipdac: spacing

* getscan detid and blocking trigger help

* udp_Dstlist det_id fixed, but rx_id invalid

* cmdApp in line with cmdLineApp (missing version, receiver_id, not creating det object in help action

* added set_command to differentiate between check_det_id and require_det_id (mixed up), args: -1 needs to check for at least one argument

* reordering

* reordering and checked till integer_command_hex

* fixed a lot more commands

* fix caller tests for eiger

* changes to tests after Bechir left

* changing .cmd to .cmdcall for the caller commands

* fixed tests for caller, still warning for setexptime about cast input

* autocomplete ran

* add moench test

* regenerating autocomplete and commands

* fixing other things from merge conflicts (renaming slsDetectorDefs to defs in commands.yaml)

* formatting

* added code injection to help (#876)

* updated 3 commands to have get output that can be put into put (#877)

* updated some commands to have get output that can be put into put

* fix tests for clkdiv

* adding help to free (#878)

* removing old commands and renaming them, (also making it work for parameters command as it was still calling cmdproxy) (#879)

* More helpful error messages for unsupported actions (#880)

* removing old commands and renaming them, (also making it work for parameters command as it was still calling cmdproxy)

* Added specific help for unsupported actions

* fixed a vetofile get special exception message. more specific warning for special exception message instead of no function warning

* added condition checking true in exceptions for special message

---------
Co-authored-by: Bechir Brahem <bachbrahem@gmail.com>
Co-authored-by: Erik Frojdh <erik.frojdh@gmail.com>
Co-authored-by: Dhanya Thattil <dhanya.thattil@psi.ch>
2023-12-13 14:43:38 +01:00

283 lines
13 KiB
Python

class CodeGenerator:
def __init__(self):
self.file = None
self.actions_dict = {
'GET': 'slsDetectorDefs::GET_ACTION',
'PUT': 'slsDetectorDefs::PUT_ACTION',
'READOUT': 'slsDetectorDefs::READOUT_ACTION',
'HELP': 'slsDetectorDefs::HELP_ACTION',
}
self.template_file = None
def open(self, path):
self.file = path.open('w')
def close(self):
self.file.close()
self.file = None
def write_line(self, line):
self.file.write(line + '\n')
def write(self, text):
self.file.write(text)
def write_opening(self, path):
"""Write the opening file for the caller.cpp file"""
self.template_file = path.open('r')
for line in self.template_file:
if "THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE" in line:
return
self.file.write(line)
def write_closing(self):
"""Write the closing file for the caller.cpp file"""
for line in self.template_file.readlines():
self.file.write(line)
self.template_file.close()
def write_header(self, in_path, out_path, commands, deprecated_commands):
"""Write the header file for the caller.h file"""
with out_path.open('w') as fp:
with in_path.open('r') as fp2:
for line in fp2:
if "THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE (1)" in line:
for command_name, command in commands.items():
if 'duplicate_function' in command and command['duplicate_function']:
continue
fp.write(f'std::string {command["function_alias"]}(int action);\n')
continue
if "THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE (2)" in line:
map_string = ''
for command_name, command in commands.items():
map_string += f'{{"{command_name}", &Caller::{command["function_alias"]}}},'
fp.write(map_string[:-1] + '\n')
continue
if "THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE (3)" in line:
for key, value in deprecated_commands.items():
fp.write(f'{{"{key}", "{value}"}},\n')
continue
fp.write(line)
def write_infer_header(self, in_path, out_path, commands):
"""Write the header file for the inferAction.h file"""
with out_path.open('w+') as fp:
with in_path.open('r') as fp2:
for line in fp2:
if "THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE (1) - DO NOT REMOVE" in line:
for command_name, command in commands.items():
if 'duplicate_function' in command and command['duplicate_function']:
continue
fp.write(f'int {command["function_alias"]}();\n')
continue
if "THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE (2) - DO NOT REMOVE" in line:
map_string = ''
for command_name, command in commands.items():
map_string += f'{{"{command_name}", &InferAction::{command["function_alias"]}}},'
fp.write(map_string[:-1] + '\n')
continue
fp.write(line)
def write_infer_cpp(self, in_path, out_path, commands, non_dist, type_dist):
"""Write the source file for the inferAction.cpp file"""
with in_path.open('r') as fp2:
for line in fp2:
if "THIS COMMENT TO BE REPLACED BY THE ACTUAL CODE (1) - DO NOT REMOVE" in line:
for command_name, command in commands.items():
if 'duplicate_function' in command and command['duplicate_function']:
continue
with function('int', f"InferAction::{command['function_alias']}", []) as f:
if (command_name, -1) in non_dist| type_dist:
self.write_line(
f'throw RuntimeError("det is disabled for command: {command_name}. Use detg or detp");')
elif not command['infer_action']:
self.write_line('throw RuntimeError("infer_action is disabled");')
else:
checked_argcs = set()
for action, action_params in command['actions'].items():
for arg in action_params['args']:
if arg['argc'] in checked_argcs:
continue
checked_argcs.add(arg['argc'])
with if_block(f'args.size() == {arg["argc"]}'):
# check if this argc is not distinguishable
if (command_name, arg["argc"]) in non_dist | type_dist:
self.write_line(
f'throw RuntimeError("det is disabled for command: {command_name} with number of arguments {arg["argc"]}. Use detg or detp");')
else:
self.write_line(f'return {self.actions_dict[action]};')
with else_block():
self.write_line(
'throw RuntimeError("Could not infer action: Wrong number of arguments");')
continue
self.write_line(line)
def write_check_arg(self):
pass
def write_arg(self, args, action, command_name):
for arg in args:
if arg['argc'] != -1:
if_block(f'args.size() == {arg["argc"]}',).__enter__()
if 'pattern_command' in arg and arg['pattern_command']:
self.write_line(f'int level = -1, iArg = 0, '
f'nGetArgs = {arg["pattern_command"]["nGetArgs"]},'
f' nPutArgs = {arg["pattern_command"]["nPutArgs"]};\nGetLevelAndUpdateArgIndex(action, '
f'"{arg["pattern_command"]["command_name"]}", level, iArg, nGetArgs,nPutArgs);'
)
if 'extra_variables' in arg:
for var in arg['extra_variables']:
codegen.write_line(f'{var["type"]} {var["name"]} = {var["value"]};')
if 'separate_time_units' in arg and arg['separate_time_units']:
self.write_line(f'std::string tmp_time({arg["separate_time_units"]["input"]});')
self.write_line(f'std::string {arg["separate_time_units"]["output"][1]}'
f' = RemoveUnit(tmp_time);')
self.write_line(f'auto {arg["separate_time_units"]["output"][0]} = '
f'StringTo < time::ns > (tmp_time,'
f' {arg["separate_time_units"]["output"][1]});')
if 'convert_to_time' in arg and arg['convert_to_time']:
self.write_line(f'auto {arg["convert_to_time"]["output"]} = '
f'StringTo < time::ns > ({", ".join(arg["convert_to_time"]["input"])});')
input_arguments = []
if 'exceptions' in arg:
for exception in arg['exceptions']:
self.write_line(f'if ({exception["condition"]}) {{ throw RuntimeError({exception["message"]}); }}')
if 'check_det_id' in arg and arg['check_det_id']:
self.write_line(
f'if (det_id != -1) {{ throw RuntimeError("Cannot execute {command_name} at module level"); }} '
)
# only used for 3 commands :(
if 'ctb_output_list' in arg:
self.write_line(f"""
std::string suffix = " {arg['ctb_output_list']['suffix']}";
auto t = det->{arg['ctb_output_list']['GETFCNLIST']}();""")
if arg['ctb_output_list']['GETFCNNAME'] != '':
self.write_line(f"""
auto names = det->{arg['ctb_output_list']['GETFCNNAME']}();
auto name_it = names.begin();""")
self.write_line("os << '[';")
with if_block(f't.size() > 0'):
self.write_line(f"""
auto it = t.cbegin();
os << ToString({arg['ctb_output_list']['printable_name']}) << ' ';
os << OutString(det->{arg['ctb_output_list']['GETFCN']}(*it++, std::vector<int>{{det_id}}))<< suffix;
while (it != t.cend()) {{
os << ", " << ToString({arg['ctb_output_list']['printable_name']}) << ' ';
os << OutString(det->{arg['ctb_output_list']['GETFCN']}(*it++, std::vector<int>{{det_id}}))<< suffix;
}}
""")
self.write_line('os << "]\\n";')
if arg['argc'] != -1:
if_block().__exit__()
return
for i in range(len(arg['input'])):
if arg['cast_input'][i]:
self.write_line(
f'auto arg{i} = StringTo<{arg["input_types"][i]}>({arg["input"][i]});')
input_arguments.append(f'arg{i}')
else:
input_arguments.append(arg["input"][i])
if 'require_det_id' in arg and arg['require_det_id']:
if 'convert_det_id' in arg and arg['convert_det_id']:
input_arguments.append("std::vector<int>{ det_id }")
else:
input_arguments.append("det_id")
input_arguments = ", ".join(input_arguments)
# call function
if arg["function"]:
if arg['store_result_in_t']:
self.write_line(f'auto t = det->{arg["function"]}({input_arguments});')
else:
self.write_line(f'det->{arg["function"]}({input_arguments});')
else:
pass #We have no function so skip block
output_args = []
for output in arg['output']:
output_args.append(output)
if len(output_args) > 0:
self.write_line(f"os << {'<< '.join(output_args)} << '\\n';")
if arg['argc'] != -1:
if_block().__exit__()
class if_block:
def __init__(self, condition="", elseif=False):
self.condition = condition
self.elseif = elseif
self.block = False
def __enter__(self):
if self.elseif:
codegen.write_line('else if (' + self.condition + ') {')
else:
codegen.write_line('if (' + self.condition + ') {')
if self.block:
codegen.write_line('{\n')
def __exit__(self, *args):
codegen.write_line('}')
if self.block:
codegen.write_line('}')
codegen.write_line('')
class else_block:
def __init__(self):
pass
def __enter__(self):
codegen.write_line('else {')
codegen.write_line('')
def __exit__(self, *args):
codegen.write_line('}')
codegen.write_line('')
class for_block:
def __init__(self, condition):
self.condition = condition
def __enter__(self):
codegen.write_line('for (' + self.condition + ') {')
codegen.write_line('')
def __exit__(self, *args):
codegen.write_line('}')
codegen.write_line('')
class function:
def __init__(self, return_type, name, args: list[tuple[str, str]]):
self.name = name
self.args = args
self.return_type = return_type
def __enter__(self):
s = ""
for arg in self.args:
arg_type, arg_name = arg
s += arg_type + ' ' + arg_name + ', '
s = s[:-2]
codegen.write_line(self.return_type + ' ' + self.name +
f'({s}) {{')
codegen.write_line('')
return self
def __exit__(self, *args):
codegen.write_line('}')
codegen.write_line('')
codegen = CodeGenerator()