#!/usr/bin/env python # Script Context Driver Generator # Author: Douglas Clowes (douglas.clowes@ansto.gov.au) Jan/Feb 2014 # vim: ft=python ts=8 sts=4 sw=4 expandtab autoindent smartindent # # This program generates Script Context Driver TCL files. # # It takes one or more "Script Context Driver Description" files. # Each file may contain one or more driver descriptions. Each driver # description will result in one TCL file. # # The name of the file produced, the TCL namespace used and names within # the file are based on the driver name givin in the driver description. # # TODO: # implement attributes and units on vars # - type part ??? # - nxalias xxxxx # - sdsinfo # check simulation works # handle environmental monitoring (emon) # - figure out how to do it # - make nodes monitorable # - make it conditional # handle the driving settling time in checkstatus # Questions: # what it the 'plain spy none' on hfactory # what is 'mugger' vs 'manager' - seems like alias? # should some hset commands be hupdate commands? # import os import ply.lex as lex import ply.yacc as yacc global Verbose global DriverDump global CodeDump global FunctionTypes global DriveableFunctionTypes global NumberOfLinesIn global NumberOfLinesOut states = ( ('tcl', 'exclusive'), ) FunctionTypes = [ 'read_function', 'write_function', 'fetch_function', 'check_function', 'pid_function', 'checkrange_function', ] DriveableFunctionTypes = [ 'halt_function', 'checklimits_function', 'checkstatus_function', ] Verbose = False DriverDump = False CodeDump = False # # Tokenizer: This recognizes the tokens which can be keywords, identifiers, # numbers or strings plus the punctuation. # # # Reserved words (keywords) in the form reserved['KEYWORD'] = 'TOKEN' # reserved = { # Driver keywords 'DRIVER' : 'DRIVER', 'VENDOR' : 'VENDOR', 'DEVICE' : 'DEVICE', 'PROTOCOL' : 'PROTOCOL', 'DRIVER_PROPERTY' : 'DRIVER_PROPERTY', 'WRAPPER_PROPERTY' : 'WRAPPER_PROPERTY', 'CLASS' : 'CLASS', 'SIMULATION_GROUP' : 'SIMULATION_GROUP', 'DEBUG_THRESHOLD' : 'DEBUG_THRESHOLD', 'CODE' : 'CODE', 'ADD_ARGS' : 'ADD_ARGS', 'MAKE_ARGS' : 'MAKE_ARGS', 'SOBJ_PRIV_TYPE' : 'SOBJ_PRIV_TYPE', 'PROTOCOL_ARGS' : 'PROTOCOL_ARGS', # Group keywords 'GROUP' : 'GROUP', 'GROUP_PROPERTY' : 'GROUP_PROPERTY', # Variable keywords 'VAR' : 'VAR', 'PROPERTY' : 'PROPERTY', 'CONTROL' : 'CONTROL', 'DATA' : 'DATA', 'NXSAVE' : 'NXSAVE', 'MUTABLE' : 'MUTABLE', 'READABLE' : 'READABLE', 'WRITEABLE' : 'WRITEABLE', 'DRIVEABLE' : 'DRIVEABLE', 'PERMLINK' : 'PERMLINK', 'TRUE' : 'TRUE', 'FALSE' : 'FALSE', # Data Types 'TYPE' : 'TYPE', 'FLOAT' : 'FLOAT', 'INT' : 'INT', 'TEXT' : 'TEXT', 'NONE' : 'NONE', # Privilege levels 'PRIV' : 'PRIV', 'SPY' : 'SPY', 'USER' : 'USER', 'MANAGER' : 'MANAGER', 'READONLY' : 'READONLY', 'INTERNAL' : 'INTERNAL', # Functions and Commands 'READ_COMMAND' : 'READ_COMMAND', 'READ_FUNCTION' : 'READ_FUNCTION', 'FETCH_FUNCTION' : 'FETCH_FUNCTION', 'WRITE_COMMAND' : 'WRITE_COMMAND', 'WRITE_FUNCTION' : 'WRITE_FUNCTION', 'CHECK_FUNCTION' : 'CHECK_FUNCTION', 'PID_FUNCTION' : 'PID_FUNCTION', 'CHECKRANGE_FUNCTION' : 'CHECKRANGE_FUNCTION', 'CHECKLIMITS_FUNCTION' : 'CHECKLIMITS_FUNCTION', 'CHECKSTATUS_FUNCTION' : 'CHECKSTATUS_FUNCTION', 'HALT_FUNCTION' : 'HALT_FUNCTION', # Value setting 'VALUE' : 'VALUE', 'ALLOWED' : 'ALLOWED', 'LOWERLIMIT' : 'LOWERLIMIT', 'UPPERLIMIT' : 'UPPERLIMIT', 'TOLERANCE' : 'TOLERANCE', 'UNITS' : 'UNITS', } # # Tokens list with keyword tokens added at the end # tokens = [ 'LBRACE', 'RBRACE', 'SLASH', 'INTEGER', 'FLOATER', 'CODE_STRING', 'TEXT_STRING1', 'TEXT_STRING2', 'EQUALS', 'ID', 'TCL_BEG', 'TCL_END', 'AT_TCL', 'AT_END', ] + list(reserved.values()) # # Token rules # t_EQUALS = r'=' t_LBRACE = r'{' t_RBRACE = r'}' t_SLASH = r'/' def t_AT_TCL(t): r'@TCL' if Verbose: print 'AT_TCL' t.lexer.begin('tcl') #return t def t_tcl_AT_END(t): r'[ \t]*@END' if Verbose: print 'AT_END' t.lexer.begin('INITIAL') #return t def t_TCL_BEG(t): r'{%%' if Verbose: print 'TCL_BEG' t.lexer.begin('tcl') return t def t_tcl_TCL_END(t): r'.*%%}' if Verbose: print 'TCL_END' t.lexer.begin('INITIAL') return t t_tcl_ignore = "" def t_tcl_CODE_STRING(t): r'.+' if t.value[0] == '@': t.value = t.value[1:] if Verbose: print 'TCL:', t.value return t def t_tcl_newline(t): r'\n+' t.lexer.lineno += t.value.count("\n") def t_tcl_error(t): print("Illegal tcl character '%s'" % t.value[0]) t.lexer.skip(1) def t_TEXT_STRING1(t): r'\'[^\']+\'' t.value = t.value[1:-1] return t def t_TEXT_STRING2(t): r"\"[^\"]+\"" t.value = t.value[1:-1] return t def t_CODE_STRING(t): r'\@.*' t.value = t.value[1:] return t def t_COMMENT(t): r'\#.*' pass # No Return Value. Token discarded def t_FLOATER(t): r'-?\d+\.\d*([eE]\d+)?' try: t.value = float(t.value) except ValueError: print "Floating value invalid:", t.value t.value = 0.0 return t def t_INTEGER(t): r'-?\d+' try: t.value = int(t.value) except ValueError: print "Integer value too large:", t.value t.value = 0 return t def t_ID(t): r'[a-zA-Z_][a-zA-Z_0-9]*' t.type = reserved.get(t.value.upper(), 'ID') # Check for reserved words # Force reserved words to lower case for map lookup and comparisson if t.value.upper() in reserved: t.type = reserved[t.value.upper()] t.value = t.value.lower() return t # Ignored characters t_ignore = " \t;" def t_newline(t): r'\n+' t.lexer.lineno += t.value.count("\n") def t_error(t): print("Illegal character '%s'" % t.value[0]) t.lexer.skip(1) # # Parser # # # Parsing rules # # # We don't yet have a need for precedence so leave it empty here # precedence = ( #('left','PLUS','MINUS'), #('left','TIMES','DIVIDE'), #('right','UMINUS'), ) # # The head token - it all reduces to this # def p_driver(p): 'driver : DRIVER id_or_str EQUALS driver_block' p[0] = [{ 'Driver' : {p[2] : p[4]}}] if Verbose: print "Driver:", p[0] global PathName global TheDrivers TheDrivers[p[2]] = p[4] + [{'PathName':PathName}] def p_driver_block(p): 'driver_block : LBRACE driver_statement_list RBRACE' p[0] = p[2] def p_driver_statement_list(p): '''driver_statement_list : driver_statement | driver_statement_list driver_statement ''' if len(p) == 2: p[0] = [p[1]] else: p[0] = p[1] + [p[2]] def p_driver_statement(p): '''driver_statement : driver_assignment | group | code | driver_property | wrapper_property ''' p[0] = p[1] def p_driver_assignment(p): ''' driver_assignment : VENDOR EQUALS id_or_str | DEVICE EQUALS id_or_str | PROTOCOL EQUALS id_or_str | CLASS EQUALS id_or_str | SIMULATION_GROUP EQUALS id_or_str | ADD_ARGS EQUALS text_string | MAKE_ARGS EQUALS text_string | SOBJ_PRIV_TYPE EQUALS text_string | PROTOCOL_ARGS EQUALS text_string | DEBUG_THRESHOLD EQUALS value ''' p[0] = { p[1] : p[3] } # # The GROUP block # def p_group(p): ''' group : GROUP group_id EQUALS LBRACE group_statement_list RBRACE ''' p[0] = { 'Group' : [{'name': p[2]}] + p[5] } def p_group_id(p): ''' group_id : id_or_str | empty ''' p[0] = p[1] def p_group_statement_list(p): ''' group_statement_list : group_statement | group_statement_list group_statement ''' if len(p) == 2: p[0] = [p[1]] else: p[0] = p[1] + [p[2]] def p_group_statement(p): '''group_statement : group_assignment | variable | group ''' p[0] = p[1] def p_group_assignment(p): '''group_assignment : group_property | var_typ_ass | property ''' p[0] = p[1] # # The VAR block # def p_variable(p): ''' variable : VAR id_or_str EQUALS LBRACE variable_statement_list RBRACE | VAR id_or_str EQUALS LBRACE RBRACE | VAR id_or_str ''' if len(p) > 6: p[0] = { 'Variable' : [{'name' : p[2]}] + p[5] } else: p[0] = { 'Variable' : [{'name' : p[2]}] } def p_variable_statement_list(p): '''variable_statement_list : variable_statement | variable_statement_list variable_statement ''' if len(p) == 2: p[0] = [p[1]] else: p[0] = p[1] + [p[2]] def p_variable_statement(p): '''variable_statement : var_typ_ass | var_val_ass | property ''' p[0] = p[1] def p_var_typ_ass(p): ''' var_typ_ass : READABLE EQUALS INTEGER | WRITEABLE EQUALS INTEGER | READ_COMMAND EQUALS text_string | READ_FUNCTION EQUALS id_or_str | FETCH_FUNCTION EQUALS id_or_str | WRITE_COMMAND EQUALS text_string | WRITE_FUNCTION EQUALS id_or_str | CHECK_FUNCTION EQUALS id_or_str | PID_FUNCTION EQUALS id_or_str | CHECKRANGE_FUNCTION EQUALS id_or_str | CHECKLIMITS_FUNCTION EQUALS id_or_str | CHECKSTATUS_FUNCTION EQUALS id_or_str | HALT_FUNCTION EQUALS id_or_str | TYPE EQUALS type_code | PRIV EQUALS priv_code | CONTROL EQUALS true_false | DATA EQUALS true_false | NXSAVE EQUALS true_false | MUTABLE EQUALS true_false ''' p[0] = { p[1] : p[3] } def p_var_path(p): ''' var_path : id_or_str | var_path SLASH id_or_str ''' if len(p) == 2: p[0] = p[1] else: p[0] = p[1] + '/' + p[3] def p_var_val_ass(p): ''' var_val_ass : VALUE EQUALS FLOATER | VALUE EQUALS text_string | VALUE EQUALS INTEGER | ALLOWED EQUALS text_string | UNITS EQUALS text_string | LOWERLIMIT EQUALS value | UPPERLIMIT EQUALS value | TOLERANCE EQUALS value | PERMLINK EQUALS text_string | DRIVEABLE EQUALS var_path ''' p[0] = { p[1] : p[3] } def p_driver_property(p): ''' driver_property : DRIVER_PROPERTY id_or_str EQUALS value ''' p[0] = { 'DriverProperty' : ( p[2], p[4] ) } def p_wrapper_property(p): ''' wrapper_property : WRAPPER_PROPERTY id_or_str EQUALS value ''' p[0] = { 'WrapperProperty' : ( p[2], p[4] ) } def p_group_property(p): ''' group_property : GROUP_PROPERTY id_or_str EQUALS value ''' p[0] = { 'GroupProperty' : ( p[2], p[4] ) } def p_property(p): ''' property : PROPERTY id_or_str EQUALS value ''' p[0] = { 'Property' : ( p[2], p[4] ) } def p_value(p): ''' value : number | id_or_str | true_false ''' p[0] = p[1] def p_number(p): ''' number : INTEGER | FLOATER ''' p[0] = p[1] def p_type_code(p): ''' type_code : FLOAT | INT | TEXT | NONE ''' p[0] = p[1] def p_priv_code(p): ''' priv_code : SPY | USER | MANAGER | READONLY | INTERNAL ''' p[0] = p[1] def p_true_false(p): ''' true_false : TRUE | FALSE ''' p[0] = p[1] # # The CODE block # def p_code(p): ''' code : CODE code_type id_or_str EQUALS LBRACE code_block RBRACE | CODE code_type id_or_str EQUALS LBRACE tcl_code_block RBRACE | CODE code_type id_or_str EQUALS TCL_BEG code_block TCL_END ''' p[0] = { 'Code' : { 'name' : p[3], 'type' : p[2], 'text' : p[6] }} def p_code_type(p): ''' code_type : READ_FUNCTION | FETCH_FUNCTION | WRITE_FUNCTION | CHECK_FUNCTION | PID_FUNCTION | CHECKRANGE_FUNCTION | CHECKLIMITS_FUNCTION | CHECKSTATUS_FUNCTION | HALT_FUNCTION | empty ''' p[0] = p[1] def p_tcl_code_block(p): ''' tcl_code_block : AT_TCL code_block AT_END ''' p[0] = p[2] def p_code_block(p): '''code_block : empty | code_block CODE_STRING ''' if len(p) == 2: p[0] = [] else: p[0] = p[1] + [p[2]] def p_id_or_str(p): ''' id_or_str : ID | text_string ''' p[0] = p[1] def p_text_string(p): ''' text_string : TEXT_STRING1 | TEXT_STRING2 ''' p[0] = p[1] def p_empty(p): ''' empty : ''' pass def p_error(t): print("Syntax error at '%s'" % t.value), t # # Utility functions # def make_path(MyVar): path = MyVar['path'] if len(path) > 0: path = path.replace('/', '_') path += '_' path += MyVar['name'] return path # # This section handles building a driver tree from the Abstract Syntax Tree # generated by the parser. The driver tree has all of the defaults and # cascading context explicitly stated to make the code generation simpler. # def init_context(): global ContextStack, ContextIndex ContextStack = [{}] ContextIndex = 0 ContextStack[ContextIndex]['type'] = 'none' ContextStack[ContextIndex]['priv'] = 'user' ContextStack[ContextIndex]['readable'] = 0 ContextStack[ContextIndex]['writeable'] = 0 ContextStack[ContextIndex]['driveable'] = None ContextStack[ContextIndex]['control'] = 'true' ContextStack[ContextIndex]['data'] = 'true' ContextStack[ContextIndex]['mutable'] = 'true' ContextStack[ContextIndex]['nxsave'] = 'true' ContextStack[ContextIndex]['read_function'] = 'rdValue' ContextStack[ContextIndex]['write_function'] = 'setValue' ContextStack[ContextIndex]['fetch_function'] = 'getValue' ContextStack[ContextIndex]['check_function'] = 'noResponse' ContextStack[ContextIndex]['checkrange_function'] = 'checkrange' ContextStack[ContextIndex]['path'] = '' def push_context(): global ContextStack, ContextIndex ContextIndex = ContextIndex + 1 if len(ContextStack) <= ContextIndex: ContextStack.append({}) ContextStack[ContextIndex] = {} for k in ContextStack[ContextIndex - 1].keys(): ContextStack[ContextIndex][k] = ContextStack[ContextIndex - 1][k] def pop_context(): global ContextStack, ContextIndex ContextIndex = ContextIndex - 1 def build_code(MyDriver, p): if Verbose: print 'Code:', p print "Function:", p['name'] MyCode = {} MyCode['name'] = p['name'] MyCode['reference_count'] = 0 if 'type' in p: MyCode['type'] = p['type'] MyCode['text'] = p['text'] if Verbose: for line in p['text']: print " Line:", line return MyCode def build_variable(MyDriver, p): global FunctionTypes global DriveableFunctionTypes if Verbose: print 'Variable:', p MyVar = {} MyVar['Property'] = {} # Copy items for this variable for item in p: if Verbose: print "Variable Item:", item for key in item.keys(): if key == 'Property': MyVar['Property'][item[key][0]] = item[key][1] else: MyVar[key] = item[key] # copy the defaults for missing items for key in ContextStack[ContextIndex]: if key == 'Property': for key2 in ContextStack[ContextIndex][key]: if key2 not in MyVar['Property']: MyVar['Property'][key2] = ContextStack[ContextIndex][key][key2] elif not key in MyVar: MyVar[key] = ContextStack[ContextIndex][key] if 'sdsinfo' not in MyVar['Property']: MyVar['Property']['sdsinfo'] = '::nexus::scobj::sdsinfo' # set the type if not explicitly set if 'type' not in MyVar['Property']: if 'driveable' in MyVar and MyVar['driveable']: MyVar['Property']['type'] = 'drivable' else: MyVar['Property']['type'] = 'part' # if this variable is driveable if 'driveable' in MyVar and MyVar['driveable']: # insert defaults for missing driveable functions if 'checklimits_function' not in MyVar: MyVar['checklimits_function'] = 'checklimits' if 'checkstatus_function' not in MyVar: MyVar['checkstatus_function'] = 'checkstatus' if 'halt_function' not in MyVar: MyVar['halt_function'] = 'halt' for func in FunctionTypes + DriveableFunctionTypes: if func in MyVar and MyVar[func] != 'none': if Verbose: print 'Var:', MyVar['name'], 'Func:', func, '=', MyVar[func] if MyVar[func] not in MyDriver['Funcs']: MyDriver['Funcs'][MyVar[func]] = { 'type' : func, 'text' : [], 'reference_count' : 0 } if Verbose: print MyVar['name'], 'Add func ' + MyVar[func], MyDriver['Funcs'][MyVar[func]] elif not MyDriver['Funcs'][MyVar[func]]['type'] == func: # allow override of type none else error message if not MyDriver['Funcs'][MyVar[func]]['type']: if Verbose: print MyVar['name'], 'Mod func type:', MyDriver['Funcs'][MyVar[func]], '= ' + func MyDriver['Funcs'][MyVar[func]]['type'] = func else: # TODO FIXME error message print 'Error: Function type mismatch: var = ' + str(MyVar) + ', code = ' + str(MyDriver['Funcs'][MyVar[func]]) + ', func = ' + str(func) MyDriver['Funcs'][MyVar[func]]['reference_count'] += 1 if 'permlink' in MyVar: device_type, node_type = MyVar['permlink'].split('.') if node_type not in MyDriver['Permlink']: MyDriver['Permlink'][node_type] = [] MyDriver['Permlink'][node_type] += [make_path(MyVar)] if Verbose: print '==>>MyVar:', MyVar return MyVar def build_group(MyDriver, p): if Verbose: print 'Group:', p[0]['name'], p push_context() MyGroup = {} MyGroup['Groups'] = {} MyGroup['Vars'] = {} # the sequence of both variables and non-variables is significant # Therefore, they have to be processed in a single sequence if p[0]['name']: if len(ContextStack[ContextIndex]['path']) > 0: ContextStack[ContextIndex]['path'] += '/' ContextStack[ContextIndex]['path'] += p[0]['name'] MyGroup['path'] = ContextStack[ContextIndex]['path'] for item in p: if 'Variable' in item: MyVar = build_variable(MyDriver, item['Variable']) MyGroup['Vars'][MyVar['name']] = MyVar elif 'Group' in item: MySubGroup = build_group(MyDriver, item['Group']) MyGroup['Groups'][MySubGroup['name']] = MySubGroup else: if Verbose: print "Group Item:", item if 'GroupProperty' in item: if 'GroupProperty' not in MyGroup: MyGroup['GroupProperty'] = {} MyGroup['GroupProperty'][item['GroupProperty'][0]] = item['GroupProperty'][1] elif 'Property' in item: if 'Property' not in MyGroup: MyGroup['Property'] = {} MyGroup['Property'][item['Property'][0]] = item['Property'][1] if 'Property' not in ContextStack[ContextIndex]: ContextStack[ContextIndex]['Property'] = {} ContextStack[ContextIndex]['Property'][item['Property'][0]] = item['Property'][1] else: for key in item: MyGroup[key] = item[key] if key in ContextStack[ContextIndex]: ContextStack[ContextIndex][key] = item[key] pop_context() adjust_group(MyGroup) return MyGroup def adjust_group(MyGroup): if Verbose: print 'ante adjust_group', MyGroup MyData = None for var in MyGroup['Vars']: if Verbose: print "Var:", MyGroup['Vars'][var] if 'data' in MyGroup['Vars'][var]: if MyGroup['Vars'][var]['data'] == 'true': MyData = 'true' if 'klass' not in MyGroup['Vars'][var]['Property']: MyGroup['Vars'][var]['Property']['klass'] = 'parameter' else: MyData = 'false' if MyData is None: for grp in MyGroup['Groups']: if Verbose: print "Grp:", MyGroup['Groups'][grp] adjust_group(MyGroup['Groups'][grp]) if 'data' in MyGroup['Groups'][grp]['GroupProperty']: if MyGroup['Groups'][grp]['GroupProperty']['data'] == 'true': MyData = 'true' else: MyData = 'false' break if MyData is not None: if 'GroupProperty' not in MyGroup: MyGroup['GroupProperty'] = {} if 'data' not in MyGroup['GroupProperty']: MyGroup['GroupProperty']['data'] = MyData if MyData: if 'klass' not in MyGroup['GroupProperty']: MyGroup['GroupProperty']['klass'] = '@none' if 'type' not in MyGroup['GroupProperty']: MyGroup['GroupProperty']['type'] = 'part' if Verbose: print 'post adjust_group', MyGroup def build_driver(MyDriver, TheTree): if Verbose: print "TheTree:", TheTree init_context() for item in [x for x in TheTree if 'Code' in x]: MyCode = build_code(MyDriver, item['Code']) MyDriver['Funcs'][MyCode['name']] = MyCode for item in [x for x in TheTree if 'Group' in x]: MyGroup = build_group(MyDriver, item['Group']) MyDriver['Groups'][MyGroup['name']] = MyGroup for item in TheTree: if Verbose: print "Driver Item:", item if 'Group' in item: continue elif 'Code' in item: continue else: if 'DriverProperty' in item: if 'DriverProperty' not in MyDriver: MyDriver['DriverProperty'] = {} MyDriver['DriverProperty'][item['DriverProperty'][0]] = item['DriverProperty'][1] continue if 'WrapperProperty' in item: if 'WrapperProperty' not in MyDriver: MyDriver['WrapperProperty'] = {} MyDriver['WrapperProperty'][item['WrapperProperty'][0]] = item['WrapperProperty'][1] continue for key in item: MyDriver[key] = item[key] for item in MyDriver['Permlink']: if len(MyDriver['Permlink'][item]) > 1: print 'Error: duplicate permlink entries for "%s"' % item, MyDriver['Permlink'][item] if Verbose: print "MyDriver:", MyDriver # # Driver Dump Functions # def dump_driver_vars(vars, indent): global FunctionTypes global DriveableFunctionTypes for item in sorted(vars): print indent + ' VAR %s = {' % item Comments = ['name', 'path'] Deferred = ['Property'] + Comments + FunctionTypes + DriveableFunctionTypes for Comment in sorted(Comments): if Comment in vars[item]: print indent + ' # %s = \'%s\'' % (Comment, vars[item][Comment]) for subitem in sorted([i for i in vars[item] if i not in Deferred]): print indent + ' %s = \'%s\'' % (subitem, vars[item][subitem]) for subitem in sorted([i for i in vars[item] if i in FunctionTypes]): print indent + ' %s = \'%s\'' % (subitem, vars[item][subitem]) for subitem in sorted([i for i in vars[item] if i in DriveableFunctionTypes]): print indent + ' %s = \'%s\'' % (subitem, vars[item][subitem]) for subitem in sorted([i for i in vars[item]['Property']]): print indent + ' Property \'%s\' = \'%s\'' % (subitem, vars[item]['Property'][subitem]) print indent + ' }' def dump_driver_groups(groups, indent): for item in sorted(groups): if item: print indent + 'GROUP ' + item + ' = {' else: print indent + 'GROUP = {' Comments = ['name', 'path'] Deferred = ['Groups', 'Vars', 'GroupProperty'] + Comments for Comment in sorted(Comments): if Comment in groups[item]: print indent + ' # %s = \'%s\'' % (Comment, groups[item][Comment]) for subitem in sorted([x for x in groups[item] if not x in Deferred]): print indent + ' ', subitem, '=', groups[item][subitem] if 'GroupProperty' in groups[item]: for subitem in groups[item]['GroupProperty']: print indent + ' GroupProperty', subitem, '=', groups[item]['GroupProperty'][subitem] dump_driver_vars(groups[item]['Vars'], indent) dump_driver_groups(groups[item]['Groups'], indent + ' ') print indent + '}' def dump_driver_funcs(funcs): for item in sorted(funcs): if 'type' in funcs[item] and funcs[item]['type']: print ' CODE ' + funcs[item]['type'] + ' ' + item + ' = {' else: print ' CODE ' + item + ' = {' for line in funcs[item]['text']: print ' @%s' % line print ' }' def dump_driver(MyDriver): print 'DRIVER ' + MyDriver['name'] + ' = {' Comments = ['PathName', 'Permlink'] Deferred = ['Groups', 'Funcs', 'Deferred', 'name'] + Comments for Comment in sorted(Comments): if Comment in MyDriver: print '# %s = \'%s\'' % (Comment, MyDriver[Comment]) for item in sorted([x for x in MyDriver if x not in Deferred]): print ' ' + item + ' =', '\'%s\'' % MyDriver[item] #print 'Groups:', MyDriver['Groups'] dump_driver_groups(MyDriver['Groups'], ' ') #print 'Funcs:', MyDriver['Funcs'] dump_driver_funcs(MyDriver['Funcs']) print '}' # # Code Generation Functions # def emit(txt): global NumberOfLinesOut NumberOfLinesOut += len(txt) for line in txt: fdo.write(line) if not line.endswith('\n'): fdo.write('\n') def put_preamble(MyDriver): txt = [] txt += ['# Generated driver for %s' % MyDriver['name']] txt += ['# vim: ft=tcl tabstop=8 softtabstop=2 shiftwidth=2 nocindent smartindent'] txt += ['#'] txt += [''] txt += ['namespace eval %s {' % MyDriver['namespace']] txt += [' set debug_threshold %s' % str( MyDriver['debug_threshold'])] if len(MyDriver['Permlink']) > 0: if 'make_args' in MyDriver and 'id' in MyDriver['make_args'].split(): pass else: txt += [' if { ![info exists ::scobj::permlink_device_counter]} {'] txt += [' set ::scobj::permlink_device_counter 0'] txt += [' }'] txt += ['}'] txt += [''] txt += ['proc %s::debug_log {tc_root debug_level debug_string} {' % MyDriver['namespace']] txt += [' set catch_status [ catch {'] txt += [' set debug_threshold [hgetpropval ${tc_root} debug_threshold]'] txt += [' if {${debug_level} >= ${debug_threshold}} {'] txt += [' set fd [open "../log/%s_[basename ${tc_root}].log" "a"]' % MyDriver['name']] txt += [' set line "[clock format [clock seconds] -format "%T"] ${debug_string}"'] txt += [' puts ${fd} "${line}"'] txt += [' close ${fd}'] txt += [' }'] txt += [' } catch_message ]'] txt += ['}'] txt += [''] txt += ['proc %s::sics_log {debug_level debug_string} {' % MyDriver['namespace']] txt += [' set catch_status [ catch {'] txt += [' set debug_threshold ${%s::debug_threshold}' % MyDriver['namespace']] txt += [' if {${debug_level} >= ${debug_threshold}} {'] txt += [' sicslog "%s::${debug_string}"' % MyDriver['namespace']] txt += [' }'] txt += [' } catch_message ]'] txt += ['}'] emit(txt) def put_write_function(MyDriver, func): txt = [''] txt += ['# function to write a parameter value on a device'] txt += ['proc %s::%s {tc_root nextState cmd_str} {' % (MyDriver['namespace'], func)] txt += [' set catch_status [ catch {'] txt += [' debug_log ${tc_root} 1 "%s tc_root=${tc_root} sct=[sct] cmd=${cmd_str}"' % func] txt += [' if { [hpropexists [sct] geterror] } {'] txt += [' hdelprop [sct] geterror'] txt += [' }'] txt += [' set par [sct target]'] txt += [' set cmd "${cmd_str}${par}"'] if func in MyDriver['Funcs'] and len(MyDriver['Funcs'][func]['text']) > 0: txt += ['# %s hook code starts' % func] txt += MyDriver['Funcs'][func]['text'] txt += ['# %s hook code ends' % func] txt += [' if { [hpropexists [sct] geterror] } {'] txt += [' debug_log ${tc_root} 9 "[sct] error: [sct geterror]"'] txt += [' error "[sct geterror]"'] txt += [' }'] else: txt += ['# %s hook code goes here' % func] txt += [' if { [hpropexists [sct] driving] } {'] txt += [' if { [hpropexists [sct] writestatus] && [sct writestatus] == "start" } {'] txt += [' sct driving 1'] txt += [' }'] txt += [' }'] txt += [' debug_log ${tc_root} 1 "%s sct send ${cmd}"' % func] txt += [' if {![string equal -nocase -length 10 ${cmd} "@@NOSEND@@"]} {'] txt += [' sct send "${cmd}"'] txt += [' }'] txt += [' return ${nextState}'] txt += [' } catch_message ]'] txt += [' handle_exception ${catch_status} ${catch_message}'] txt += ['}'] emit(txt) def put_check_function(MyDriver, func): txt = [''] txt += ['# function to check the write parameter on a device'] txt += ['proc %s::%s {tc_root} {' % (MyDriver['namespace'], func)] txt += [' set catch_status [ catch {'] txt += [' debug_log ${tc_root} 1 "%s tc_root=${tc_root} sct=[sct] resp=[sct result]"' % func] if func in MyDriver['Funcs'] and len(MyDriver['Funcs'][func]['text']) > 0: txt += ['# %s hook code starts' % func] txt += MyDriver['Funcs'][func]['text'] txt += ['# %s hook code ends' % func] else: txt += ['# %s hook code goes here' % func] txt += [' return "idle"'] txt += [' } catch_message ]'] txt += [' handle_exception ${catch_status} ${catch_message}'] txt += ['}'] emit(txt) def put_fetch_function(MyDriver, func): txt = [''] txt += ['# function to request the read of a parameter on a device'] txt += ['proc %s::%s {tc_root nextState cmd_str} {' % (MyDriver['namespace'], func)] txt += [' set catch_status [ catch {'] txt += [' debug_log ${tc_root} 1 "%s tc_root=${tc_root} sct=[sct] cmd=${cmd_str}"' % func] txt += [' if { [hpropexists [sct] geterror] } {'] txt += [' hdelprop [sct] geterror'] txt += [' }'] txt += [' set cmd "${cmd_str}"'] if func in MyDriver['Funcs'] and len(MyDriver['Funcs'][func]['text']) > 0: txt += ['# %s hook code starts' % func] txt += MyDriver['Funcs'][func]['text'] txt += ['# %s hook code ends' % func] txt += [' if { [hpropexists [sct] geterror] } {'] txt += [' debug_log ${tc_root} 9 "[sct] error: [sct geterror]"'] txt += [' error "[sct geterror]"'] txt += [' }'] else: txt += ['# %s hook code goes here' % func] txt += [' debug_log ${tc_root} 1 "%s sct send ${cmd}"' % func] txt += [' if {![string equal -nocase -length 10 ${cmd} "@@NOSEND@@"]} {'] txt += [' sct send "${cmd}"'] txt += [' }'] txt += [' return ${nextState}'] txt += [' } catch_message ]'] txt += [' handle_exception ${catch_status} ${catch_message}'] txt += ['}'] emit(txt) def put_read_function(MyDriver, func): txt = [''] txt += ['# function to parse the read of a parameter on a device'] txt += ['proc %s::%s {tc_root} {' % (MyDriver['namespace'], func)] txt += [' set catch_status [ catch {'] txt += [' debug_log ${tc_root} 1 "%s tc_root=${tc_root} sct=[sct] result=[sct result]"' % func] txt += [' if { [hpropexists [sct] geterror] } {'] txt += [' hdelprop [sct] geterror'] txt += [' }'] txt += [' set data [sct result]'] txt += [' set nextState "idle"'] txt += [' if {[string equal -nocase -length 7 ${data} "ASCERR:"]} {'] txt += [' # the protocol driver has reported an error'] txt += [' sct geterror "${data}"'] txt += [' error "[sct geterror]"'] txt += [' }'] if func in MyDriver['Funcs'] and len(MyDriver['Funcs'][func]['text']) > 0: txt += ['# %s hook code starts' % func] txt += MyDriver['Funcs'][func]['text'] txt += ['# %s hook code ends' % func] txt += [' if { [hpropexists [sct] geterror] } {'] txt += [' debug_log ${tc_root} 9 "[sct] error: [sct geterror]"'] txt += [' error "[sct geterror]"'] txt += [' }'] else: txt += ['# %s hook code goes here' % func] txt += [' if { ${data} != [sct oldval] } {'] txt += [' debug_log ${tc_root} 1 "[sct] changed to new:${data}, from old:[sct oldval]"'] txt += [' sct oldval ${data}'] txt += [' sct update ${data}'] txt += [' sct utime readtime'] txt += [' }'] txt += [' return ${nextState}'] txt += [' } catch_message ]'] txt += [' handle_exception ${catch_status} ${catch_message}'] txt += ['}'] emit(txt) def put_checkrange_function(MyDriver, func): txt = [''] txt += ['# check function for hset change'] txt += ['proc %s::%s {tc_root} {' % (MyDriver['namespace'], func)] txt += [' set catch_status [ catch {'] txt += [' debug_log ${tc_root} 1 "%s tc_root=${tc_root} sct=[sct] target=[sct target]"' % func] txt += [' set setpoint [sct target]'] txt += [' if { [hpropexists [sct] lowerlimit] } {'] txt += [' set lolimit [sct lowerlimit]'] txt += [' } else {'] txt += [' # lowerlimit not set, use target'] txt += [' set lolimit [sct target]'] txt += [' }'] txt += [' if { [hpropexists [sct] upperlimit] } {'] txt += [' set hilimit [sct upperlimit]'] txt += [' } else {'] txt += [' # upperlimit not set, use target'] txt += [' set hilimit [sct target]'] txt += [' }'] if func in MyDriver['Funcs'] and len(MyDriver['Funcs'][func]['text']) > 0: txt += ['# %s hook code starts' % func] txt += MyDriver['Funcs'][func]['text'] txt += ['# %s hook code ends' % func] else: txt += ['# %s hook code goes here' % func] txt += [' if { ${setpoint} < ${lolimit} || ${setpoint} > ${hilimit} } {'] txt += [' error "setpoint ${setpoint} violates limits (${lolimit}..${hilimit}) on [sct]"'] txt += [' }'] txt += [' return OK'] txt += [' } catch_message ]'] txt += [' handle_exception ${catch_status} ${catch_message}'] txt += ['}'] emit(txt) def put_checklimits_function(MyDriver, func): txt = [''] txt += ['# checklimits function for driveable interface'] txt += ['proc %s::%s {tc_root} {' % (MyDriver['namespace'], func)] txt += [' set catch_status [ catch {'] txt += [' debug_log ${tc_root} 1 "%s tc_root=${tc_root} sct=[sct] target=[sct target]"' % func] txt += [' set setpoint [sct target]'] txt += [' if { [hpropexists [sct] lowerlimit] } {'] txt += [' set lolimit [sct lowerlimit]'] txt += [' } else {'] txt += [' # lowerlimit not set, use target'] txt += [' set lolimit [sct target]'] txt += [' }'] txt += [' if { [hpropexists [sct] upperlimit] } {'] txt += [' set hilimit [sct upperlimit]'] txt += [' } else {'] txt += [' # upperlimit not set, use target'] txt += [' set hilimit [sct target]'] txt += [' }'] if func in MyDriver['Funcs'] and len(MyDriver['Funcs'][func]['text']) > 0: txt += ['# %s hook code starts' % func] txt += MyDriver['Funcs'][func]['text'] txt += ['# %s hook code ends' % func] else: txt += ['# %s hook code goes here' % func] txt += [' if { ${setpoint} < ${lolimit} || ${setpoint} > ${hilimit} } {'] txt += [' sct driving 0'] txt += [' error "setpoint ${setpoint} violates limits (${lolimit}..${hilimit}) on [sct]"'] txt += [' }'] txt += [' return OK'] txt += [' } catch_message ]'] txt += [' handle_exception ${catch_status} ${catch_message}'] txt += ['}'] emit(txt) def put_checkstatus_function(MyDriver, func): txt = [''] txt += ['# checkstatus function for driveable interface'] txt += ['proc %s::%s {tc_root} {' % (MyDriver['namespace'], func)] txt += [' set catch_status [ catch {'] if func in MyDriver['Funcs'] and len(MyDriver['Funcs'][func]['text']) > 0: txt += ['# %s hook code starts' % func] txt += MyDriver['Funcs'][func]['text'] txt += ['# %s hook code ends' % func] else: txt += ['# %s hook code goes here' % func] txt += [' if {[sct driving]} {'] txt += [' set sp "[sct target]"'] txt += [' set pv "[hval ${tc_root}/[sct driveable]]"'] txt += [' if { abs(${pv} - ${sp}) <= [sct tolerance] } {'] txt += [' if { [hpropexists [sct] settle_time] } {'] txt += [' if { [hpropexists [sct] settle_time_start] } {'] txt += [' if { [sct utime] - [sct settle_time_start] >= [sct settle_time]} {'] txt += [' sct driving 0'] txt += [' return "idle"'] txt += [' }'] txt += [' return "busy"'] txt += [' } else {'] txt += [' sct utime settle_time_start'] txt += [' return "busy"'] txt += [' }'] txt += [' }'] txt += [' sct driving 0'] txt += [' return "idle"'] txt += [' }'] txt += [' if { [hpropexists [sct] settle_time_start] } {'] txt += [' hdelprop [sct] settle_time_start'] txt += [' }'] txt += [' return "busy"'] txt += [' } else {'] txt += [' return "idle"'] txt += [' }'] txt += [' } catch_message ]'] txt += [' handle_exception ${catch_status} ${catch_message}'] txt += ['}'] emit(txt) def put_halt_function(MyDriver, func): txt = [''] txt += ['# halt function for driveable interface'] txt += ['proc %s::%s {tc_root} {' % (MyDriver['namespace'], func)] txt += [' set catch_status [ catch {'] txt += [' debug_log ${tc_root} 1 "%s tc_root=${tc_root} sct=[sct] driving=[sct driving]"' % func] txt += [' ### TODO hset [sct] [hval [sct]]'] if func in MyDriver['Funcs'] and len(MyDriver['Funcs'][func]['text']) > 0: txt += ['# %s hook code starts' % func] txt += MyDriver['Funcs'][func]['text'] txt += ['# %s hook code ends' % func] else: txt += ['# %s hook code goes here' % func] txt += [' sct driving 0'] txt += [' return "idle"'] txt += [' } catch_message ]'] txt += [' handle_exception ${catch_status} ${catch_message}'] txt += ['}'] emit(txt) def put_pid_function(MyDriver, func): txt = [''] txt += ['# pid function for PID control'] txt += ['proc %s::%s {tc_root sp pv} {' % (MyDriver['namespace'], func)] txt += [' set catch_status [ catch {'] txt += [' debug_log ${tc_root} 1 "%s tc_root=${tc_root} sct=[sct] pv=${pv} sp=${sp}"' % func] txt += [' sct pid_error [expr {${sp} - ${pv}}]'] txt += [' set p_value [expr {[sct pid_pvalue] * [sct pid_error]}]'] txt += [' set d_value [expr {[sct pid_dvalue] * (${pv} - [sct oldval])}]'] txt += [' sct pid_deriv [sct pid_error]'] txt += [' sct pid_integ [expr {[sct pid_integ] + [sct pid_error]}]'] txt += [' if { [sct pid_integ] > [sct pid_imax] } {'] txt += [' sct pid_integ [sct pid_imax]'] txt += [' }'] txt += [' if { [sct pid_integ] < -[sct pid_imax] } {'] txt += [' sct pid_integ -[sct pid_imax]'] txt += [' }'] txt += [' set i_value [expr {[sct pid_ivalue] * [sct pid_integ]}]'] txt += [' set pid [expr {${p_value} + ${i_value} + ${d_value}}]'] if func in MyDriver['Funcs'] and len(MyDriver['Funcs'][func]['text']) > 0: txt += ['# %s hook code starts' % func] txt += MyDriver['Funcs'][func]['text'] txt += ['# %s hook code ends' % func] else: txt += ['# %s hook code goes here' % func] txt += [' sct pid_output ${pid}'] txt += [' } catch_message ]'] txt += [' handle_exception ${catch_status} ${catch_message}'] txt += [' return ${pid}'] txt += ['}'] emit(txt) def put_group(MyDriver, MyGroup): readable_or_writeable = False txt = [] if MyGroup['name']: txt += [''] txt += [' hfactory ${scobj_hpath}/%s plain spy none' % MyGroup['path']] if 'GroupProperty' in MyGroup: for key in sorted(MyGroup['GroupProperty']): txt += [' hsetprop ${scobj_hpath}/%s %s "%s"' % (MyGroup['path'], key, MyGroup['GroupProperty'][key])] groupname = MyGroup['path'] + '/' else: groupname = '' for var in sorted(MyGroup['Vars']): txt += [''] MyVar = MyGroup['Vars'][var] nodename = groupname + MyVar['name'] # Check driveable attributes are present if required if 'driveable' in MyVar and MyVar['driveable']: for attr in ('lowerlimit', 'upperlimit', 'tolerance'): if attr not in MyVar: msg = 'Driveable: %s does not have required attribute: %s' % (nodename, attr) print 'Warning:', msg txt += [' # Warning: ' + msg] # Check PID attributes are present if required if 'pid_function' in MyVar: for attr in ('pid_pvalue', 'pid_ivalue', 'pid_dvalue', 'pid_imax', 'pid_error', 'pid_deriv', 'pid_integ', ): if attr not in MyVar['Property']: msg = 'PID: %s does not have required attribute: %s' % (nodename, attr) print 'Warning:', msg txt += [' # Warning: ' + msg] txt += [' hfactory ${scobj_hpath}/%s plain %s %s' % (nodename, MyVar['priv'], MyVar['type'])] if MyVar['readable'] > 0: readable_or_writeable = True fetch_func = MyVar['fetch_function'] if fetch_func == 'none': fetch_func = 'getValue' read_func = MyVar['read_function'] read_command = MyVar['read_command'] txt += [' hsetprop ${scobj_hpath}/%s read ${ns}::%s ${scobj_hpath} %s {%s}' % (nodename, fetch_func, read_func, read_command)] txt += [' hsetprop ${scobj_hpath}/%s %s ${ns}::%s ${scobj_hpath}' % (nodename, read_func, read_func)] if MyVar['writeable'] > 0 or MyVar['driveable']: readable_or_writeable = True check_func = MyVar['check_function'] checkrange_func = MyVar['checkrange_function'] write_func = MyVar['write_function'] if 'write_command' in MyVar: write_command = MyVar['write_command'] else: write_command = '' txt += [' hsetprop ${scobj_hpath}/%s write ${ns}::%s ${scobj_hpath} %s {%s}' % (nodename, write_func, check_func, write_command)] txt += [' hsetprop ${scobj_hpath}/%s %s ${ns}::%s ${scobj_hpath}' % (nodename, check_func, check_func)] txt += [' hsetprop ${scobj_hpath}/%s check ${ns}::%s ${scobj_hpath}' % (nodename, checkrange_func)] if MyVar['driveable']: halt_func = MyVar['halt_function'] checklimits_func = MyVar['checklimits_function'] checkstatus_func = MyVar['checkstatus_function'] txt += [' hsetprop ${scobj_hpath}/%s driving 0' % nodename] txt += [' hsetprop ${scobj_hpath}/%s checklimits ${ns}::%s ${scobj_hpath}' % (nodename, checklimits_func)] txt += [' hsetprop ${scobj_hpath}/%s checkstatus ${ns}::%s ${scobj_hpath}' % (nodename, checkstatus_func)] txt += [' hsetprop ${scobj_hpath}/%s halt ${ns}::%s ${scobj_hpath}' % (nodename, halt_func)] txt += [' hsetprop ${scobj_hpath}/%s driveable %s' % (nodename, MyVar['driveable'])] if 'control' in MyVar: txt += [' hsetprop ${scobj_hpath}/%s control %s' % (nodename, MyVar['control'])] if 'data' in MyVar: txt += [' hsetprop ${scobj_hpath}/%s data %s' % (nodename, MyVar['data'])] if 'mutable' in MyVar: txt += [' hsetprop ${scobj_hpath}/%s mutable %s' % (nodename, MyVar['mutable'])] if 'nxsave' in MyVar: txt += [' hsetprop ${scobj_hpath}/%s nxsave %s' % (nodename, MyVar['nxsave'])] if 'lowerlimit' in MyVar: txt += [' hsetprop ${scobj_hpath}/%s lowerlimit %s' % (nodename, MyVar['lowerlimit'])] if 'upperlimit' in MyVar: txt += [' hsetprop ${scobj_hpath}/%s upperlimit %s' % (nodename, MyVar['upperlimit'])] if 'tolerance' in MyVar: txt += [' hsetprop ${scobj_hpath}/%s tolerance %s' % (nodename, MyVar['tolerance'])] if 'units' in MyVar: txt += [' hsetprop ${scobj_hpath}/%s units %s' % (nodename, MyVar['units'])] if 'allowed' in MyVar: txt += [' hsetprop ${scobj_hpath}/%s values %s' % (nodename, MyVar['allowed'])] if 'permlink' in MyVar: device_type, node_type = MyVar['permlink'].split('.') if device_type.startswith("#"): if 'make_args' in MyDriver and 'permlink' in MyDriver['make_args'].split(): idx = int(device_type[1:]) device_type = '[string index ${permlink} %d]' % idx else: print 'Error: permlink required in make_ags' if 'make_args' in MyDriver and 'id' in MyDriver['make_args'].split(): permlink = device_type + '[format "%02d" ${id}]' + node_type else: permlink = device_type + '${permlink_device_number}' + node_type txt += [' hsetprop ${scobj_hpath}/%s permlink data_set "%s"' % (nodename, permlink)] txt += [' hsetprop ${scobj_hpath}/%s @description "%s"' % (nodename, permlink)] if 'value' in MyVar: txt += [' hsetprop ${scobj_hpath}/%s oldval %s' % (nodename, MyVar['value'])] txt += [' hset ${scobj_hpath}/%s %s' % (nodename, MyVar['value'])] else: if MyVar['type'] == 'none': pass elif MyVar['type'] == 'int': txt += [' hsetprop ${scobj_hpath}/%s oldval 0' % nodename] elif MyVar['type'] == 'float': txt += [' hsetprop ${scobj_hpath}/%s oldval 0.0' % nodename] else: txt += [' hsetprop ${scobj_hpath}/%s oldval UNKNOWN' % nodename] for key in sorted(MyVar['Property']): txt += [' hsetprop ${scobj_hpath}/%s %s "%s"' % (nodename, key, MyVar['Property'][key])] # Generate __ at runtime for nxalias if 'nxalias' not in MyVar['Property']: nxalias = '${name}_' + make_path(MyVar) txt += [' hsetprop ${scobj_hpath}/%s nxalias "%s"' % (nodename, nxalias)] if not MyGroup['name']: if 'GroupProperty' in MyGroup: txt += [''] for key in sorted(MyGroup['GroupProperty']): txt += [' hsetprop ${scobj_hpath} %s "%s"' % (key, MyGroup['GroupProperty'][key])] if readable_or_writeable: txt += [''] txt += [' if {[string equal -nocase "${simulation_flag}" "false"]} {'] for var in sorted(MyGroup['Vars']): MyVar = MyGroup['Vars'][var] nodename = groupname + MyVar['name'] if MyVar['readable'] > 0: poll_period = MyVar['readable'] if poll_period < 1: poll_period = 1 if poll_period > 3600: poll_period = 3600 txt += [' ${sct_controller} poll ${scobj_hpath}/%s %s' % (nodename, poll_period)] for var in sorted(MyGroup['Vars']): MyVar = MyGroup['Vars'][var] nodename = groupname + MyVar['name'] if MyVar['writeable'] > 0 or MyVar['driveable']: txt += [' ${sct_controller} write ${scobj_hpath}/%s' % nodename] if MyVar['driveable']: # Generate __ at runtime for driveable driveable = '${name}_' + make_path(MyVar) MyDriver['Deferred'] += ['ansto_makesctdrive %s ${scobj_hpath}/%s ${scobj_hpath}/%s ${sct_controller}' % (driveable, nodename, MyVar['driveable'])] txt += [' } else {'] txt += [' %s::sics_log 9 "simulation_flag=${simulation_flag} => No poll/write for %s"' % (MyDriver['namespace'], MyDriver['name'])] txt += [' }'] for grp in sorted(MyGroup['Groups']): txt += put_group(MyDriver, MyGroup['Groups'][grp]) return txt def put_mkDriver(MyDriver): txt = [''] if 'make_args' in MyDriver: line = 'proc %s::mkDriver { sct_controller name device_class simulation_flag ip_address tcp_port %s } {' % (MyDriver['namespace'], MyDriver['make_args']) else: line = 'proc %s::mkDriver { sct_controller name device_class simulation_flag ip_address tcp_port } {' % (MyDriver['namespace']) txt += [line] if 'make_args' in MyDriver: make_args = ' '.join(["${%s}"%arg for arg in MyDriver['make_args'].split()]) txt += [' %s::sics_log 9 "%s::mkDriver ${sct_controller} ${name} ${device_class} ${simulation_flag} ${ip_address} ${tcp_port} %s"' % (MyDriver['namespace'], MyDriver['namespace'], make_args)] else: txt += [' %s::sics_log 9 "%s::mkDriver ${sct_controller} ${name} ${device_class} ${simulation_flag} ${ip_address} ${tcp_port}"' % (MyDriver['namespace'], MyDriver['namespace'])] txt += [' set ns "[namespace current]"'] txt += [' set catch_status [ catch {'] txt += [''] func = 'mkWrapper' if func in MyDriver['Funcs']: txt += ['# %s hook code starts' % func] txt += MyDriver['Funcs'][func]['text'] txt += ['# %s hook code ends' % func] else: if len(MyDriver['Permlink']) > 0: if 'make_args' in MyDriver and 'id' in MyDriver['make_args'].split(): pass else: txt += [' set permlink_device_number [format "%02d" [incr ::scobj::permlink_device_counter]]'] txt += [''] if 'sobj_priv_type' in MyDriver: priv_type = MyDriver['sobj_priv_type'].split() ms_line = ' MakeSICSObj ${name} SCT_OBJECT %s %s' % (priv_type[0], priv_type[1]) else: ms_line = ' MakeSICSObj ${name} SCT_OBJECT' txt += [ms_line] txt += [''] txt += [' sicslist setatt ${name} klass ${device_class}'] txt += [' sicslist setatt ${name} long_name ${name}'] if 'DriverProperty' in MyDriver: for key in MyDriver['DriverProperty']: txt += [' sicslist setatt ${name} %s "%s"' % (key, MyDriver['DriverProperty'][key])] txt += [''] txt += [' set scobj_hpath /sics/${name}'] for group in sorted(MyDriver['Groups']): txt += put_group(MyDriver, MyDriver['Groups'][group]) txt += [' hsetprop ${scobj_hpath} klass ${device_class}'] txt += [' hsetprop ${scobj_hpath} data true'] txt += [' hsetprop ${scobj_hpath} debug_threshold %s' % str(MyDriver['debug_threshold'])] if len(MyDriver['Deferred']) > 0: txt += [' if {[string equal -nocase "${simulation_flag}" "false"]} {'] for line in MyDriver['Deferred']: txt += [' ' + line] txt += [' }'] func = 'mkDriver' if func in MyDriver['Funcs']: txt += ['# %s hook code starts' % func] txt += MyDriver['Funcs'][func]['text'] txt += ['# %s hook code ends' % func] else: txt += ['# %s hook code goes here' % func] txt += [' } catch_message ]'] txt += [' handle_exception ${catch_status} ${catch_message}'] txt += ['}'] emit(txt) def put_postamble(MyDriver): txt = [''] if 'add_args' in MyDriver: line = 'proc %s::add_driver {name device_class simulation_flag ip_address tcp_port %s} {' % (MyDriver['namespace'], MyDriver['add_args']) else: line = 'proc %s::add_driver {name device_class simulation_flag ip_address tcp_port} {' % (MyDriver['namespace']) txt += [line] txt += [' set catch_status [ catch {'] if 'make_args' in MyDriver: make_args = ' '.join(["${%s}"%arg for arg in MyDriver['make_args'].split()]) txt += [' %s::sics_log 9 "%s::add_driver ${name} ${device_class} ${simulation_flag} ${ip_address} ${tcp_port} %s"' % (MyDriver['namespace'], MyDriver['namespace'], make_args)] else: txt += [' %s::sics_log 9 "%s::add_driver ${name} ${device_class} ${simulation_flag} ${ip_address} ${tcp_port}"' % (MyDriver['namespace'], MyDriver['namespace'])] if ('WrapperProperty' in MyDriver) and ('nosctcontroller' in MyDriver['WrapperProperty']): txt += [' %s::sics_log 9 "No sctcontroller for %s"' % (MyDriver['namespace'], MyDriver['name'])] else: txt += [' if {[string equal -nocase "${simulation_flag}" "false"]} {'] txt += [' if {[string equal -nocase "aqadapter" "${ip_address}"]} {'] txt += [' %s::sics_log 9 "makesctcontroller sct_${name} aqadapter ${tcp_port}"' % MyDriver['namespace']] txt += [' makesctcontroller sct_${name} aqadapter ${tcp_port}'] txt += [' } else {'] if 'protocol_args' in MyDriver: protocol_args = MyDriver['protocol_args'].replace('\\', '\\\\').replace('"', '\\"') txt += [' %s::sics_log 9 "makesctcontroller sct_${name} %s ${ip_address}:${tcp_port} %s"' % (MyDriver['namespace'], MyDriver['protocol'], protocol_args)] txt += [' makesctcontroller sct_${name} %s ${ip_address}:${tcp_port} %s' % (MyDriver['protocol'], MyDriver['protocol_args'])] else: txt += [' %s::sics_log 9 "makesctcontroller sct_${name} %s ${ip_address}:${tcp_port}"' % (MyDriver['namespace'], MyDriver['protocol'])] txt += [' makesctcontroller sct_${name} %s ${ip_address}:${tcp_port}' % MyDriver['protocol']] txt += [' }'] txt += [' } else {'] txt += [' %s::sics_log 9 "simulation_flag={simulation_flag} => No sctcontroller for %s"' % (MyDriver['namespace'], MyDriver['name'])] txt += [' }'] if 'make_args' in MyDriver: make_args = ' '.join(["${%s}"%arg for arg in MyDriver['make_args'].split()]) txt += [' %s::sics_log 1 "%s::mkDriver sct_${name} ${name} ${device_class} ${simulation_flag} ${ip_address} ${tcp_port} %s"' % (MyDriver['namespace'], MyDriver['namespace'], make_args)] txt += [' %s::mkDriver sct_${name} ${name} ${device_class} ${simulation_flag} ${ip_address} ${tcp_port} %s' % (MyDriver['namespace'], make_args)] else: txt += [' %s::sics_log 1 "%s::mkDriver sct_${name} ${name} ${device_class} ${simulation_flag} ${ip_address} ${tcp_port}"' % (MyDriver['namespace'], MyDriver['namespace'])] txt += [' %s::mkDriver sct_${name} ${name} ${device_class} ${simulation_flag} ${ip_address} ${tcp_port}' % (MyDriver['namespace'])] # TODO #txt += [' %s::sics_log "makesctemon ${name} /sics/${name}/emon/monmode /sics/${name}/emon/isintol /sics/${name}/emon/errhandler"' % (MyDriver['namespace'])] # txt += [' makesctemon ${name} /sics/${name}/emon/monmode /sics/${name}/emon/isintol /sics/${name}/emon/errhandler'] txt += [' } catch_message ]'] txt += [' handle_exception ${catch_status} ${catch_message}'] txt += ['}'] txt += [''] txt += ['namespace eval %s {' % MyDriver['namespace']] txt += [' namespace export debug_threshold'] txt += [' namespace export debug_log'] txt += [' namespace export sics_log'] txt += [' namespace export mkDriver'] txt += [' namespace export add_driver'] txt += ['}'] txt += [''] if 'add_args' in MyDriver: line = 'proc add_%s {name ip_address tcp_port %s} {' % (MyDriver['name'], MyDriver['add_args']) else: line = 'proc add_%s {name ip_address tcp_port} {' % MyDriver['name'] txt += [line] txt += [' set simulation_flag "[string tolower [SplitReply [%s]]]"' % MyDriver['simulation_group']] line = ' %s::add_driver ${name} "%s" "${simulation_flag}" ${ip_address} ${tcp_port}' % (MyDriver['namespace'], MyDriver['class']) if 'add_args' in MyDriver: for arg in MyDriver['add_args'].split(): line += ' "${%s}"' % arg txt += [line] txt += ['}'] txt += [''] txt += ['clientput "file evaluation of sct_%s.tcl"' % MyDriver['name']] txt += ['%s::sics_log 9 "file evaluation of sct_%s.tcl"' % (MyDriver['namespace'], MyDriver['name'])] emit(txt) def put_read_config(MyDriver): txt = [''] txt += ['proc %s::read_config {} {' % MyDriver['namespace']] txt += [' set catch_status [ catch {'] txt += [' set ns "%s"' % MyDriver['namespace']] txt += [' dict for {k u} $::config_dict {'] txt += [' if { [dict exists $u "implementation"] } {'] txt += [' set simulation_flag "[string tolower [SplitReply [%s]]]"' % MyDriver['simulation_group']] txt += [' set device_class "%s"' % MyDriver['class']] txt += [' if { !([dict exists $u "name"] && [dict exists $u "enabled"]) } {'] txt += [' continue'] txt += [' }'] txt += [' set enabled [string tolower [dict get $u "enabled"]]'] txt += [' if { ! ([string equal -nocase $enabled "true" ] || [string equal -nocase $enabled "always"]) } {'] txt += [' continue'] txt += [' }'] txt += [' if { [dict exists $u "simulation_group"] } {'] txt += [' set simulation_flag [SplitReply [[string tolower [dict get $u "simulation_group"]]]]'] txt += [' }'] txt += [' if { [dict exists $u "device_class"] } {'] txt += [' set device_class "[dict get $u "device_class"]"'] txt += [' }'] txt += [' set name [dict get $u name]'] txt += [' set implementation [dict get $u "implementation"]'] txt += [' if { !([dict exists $::config_dict $implementation]) } {'] txt += [' continue'] txt += [' }'] txt += [' set v [dict get $::config_dict $implementation]'] txt += [' if { !([dict exists $v "driver"]) } {'] txt += [' continue'] txt += [' }'] txt += [' if { [string equal -nocase [dict get $v "driver"] "%s"] } {' % MyDriver['name']] txt += [' if { ![string equal -nocase "${simulation_flag}" "false"] } {'] txt += [' set asyncqueue "null"'] txt += [' ${ns}::sics_log 9 "simulation_flag=${simulation_flag} => using null asyncqueue"'] txt += [' } elseif { [dict exists $v "asyncqueue"] } {'] txt += [' set asyncqueue [dict get $v "asyncqueue"]'] txt += [' if { [string equal -nocase ${asyncqueue} "sct"] } {'] txt += [' set ip_address [dict get $v ip]'] txt += [' set tcp_port [dict get $v port]'] txt += [' }'] txt += [' } else {'] txt += [' if { [dict exists $v "asyncprotocol"] } {'] txt += [' set asyncprotocol [dict get $v "asyncprotocol"]'] txt += [' } else {'] txt += [' set asyncprotocol ${name}_protocol'] txt += [' MakeAsyncProtocol ${asyncprotocol}'] txt += [' if { [dict exists $v "terminator"] } {'] txt += [' ${asyncprotocol} sendterminator "[dict get $v "terminator"]"'] txt += [' ${asyncprotocol} replyterminator "[dict get $v "terminator"]"'] txt += [' }'] txt += [' }'] txt += [' set asyncqueue ${name}_queue'] txt += [' set ip_address [dict get $v ip]'] txt += [' set tcp_port [dict get $v port]'] txt += [' MakeAsyncQueue ${asyncqueue} ${asyncprotocol} ${ip_address} ${tcp_port}'] txt += [' if { [dict exists $v "timeout"] } {'] txt += [' ${asyncqueue} timeout "[dict get $v "timeout"]"'] txt += [' }'] txt += [' }'] if 'make_args' in MyDriver: txt += [' set arg_list [list]'] txt += [' set missing_list [list]'] txt += [' foreach arg {' + MyDriver['make_args'] + '} {'] txt += [' if {[dict exists $u $arg]} {'] txt += [' lappend arg_list "[dict get $u $arg]"'] txt += [' } elseif {[dict exists $v $arg]} {'] txt += [' lappend arg_list "[dict get $v $arg]"'] txt += [' } else {'] txt += [' ${ns}::sics_log 9 "Missing configuration value $arg"'] txt += [' lappend missing_list $arg'] txt += [' }'] txt += [' }'] txt += [' if { [llength $missing_list] > 0 } {'] txt += [' error "$name is missing configuration values $missing_list"'] txt += [' }'] txt += [' if { [string equal -nocase ${asyncqueue} "sct"] } {'] txt += [' ${ns}::add_driver ${name} ${device_class} ${simulation_flag} ${ip_address} ${tcp_port} {*}$arg_list'] txt += [' } else {'] txt += [' ${ns}::add_driver ${name} ${device_class} ${simulation_flag} "aqadapter" ${asyncqueue} {*}$arg_list'] txt += [' }'] else: txt += [' if { [string equal -nocase ${asyncqueue} "sct"] } {'] txt += [' ${ns}::add_driver ${name} ${device_class} ${simulation_flag} ${ip_address} ${tcp_port}'] txt += [' } else {'] txt += [' ${ns}::add_driver ${name} ${device_class} ${simulation_flag} "aqadapter" ${asyncqueue}'] txt += [' }'] txt += [' }'] txt += [' }'] txt += [' }'] txt += [' } catch_message ]'] txt += [' handle_exception ${catch_status} ${catch_message}'] txt += ['}'] emit(txt) def put_check_config(MyDriver): txt = [''] txt += ['if { [info exists ::config_dict] } {'] txt += [' %s::read_config' % MyDriver['namespace']] txt += ['} else {'] txt += [' %s::sics_log 5 "No config dict"' % MyDriver['namespace']] txt += ['}'] emit(txt) def put_standard_code(MyDriver): # emit all of the functions in Funcs for func in sorted(MyDriver['Funcs']): theFunc = MyDriver['Funcs'][func] # Don't generate functions which are not referenced #if theFunc['reference_count'] == 0: # continue if theFunc['type'] == 'read_function': put_read_function(MyDriver, func) elif theFunc['type'] == 'write_function': put_write_function(MyDriver, func) elif theFunc['type'] == 'fetch_function': put_fetch_function(MyDriver, func) elif theFunc['type'] == 'check_function': put_check_function(MyDriver, func) elif theFunc['type'] == 'checkrange_function': put_checkrange_function(MyDriver, func) elif theFunc['type'] == 'checklimits_function': put_checklimits_function(MyDriver, func) elif theFunc['type'] == 'checkstatus_function': put_checkstatus_function(MyDriver, func) elif theFunc['type'] == 'halt_function': put_halt_function(MyDriver, func) elif theFunc['type'] == 'pid_function': put_pid_function(MyDriver, func) def generate_driver(MyDriver): global NumberOfLinesOut global fdo NumberOfLinesOut = 0 full_filename = filename = "sct_%s.tcl" % MyDriver['name'] if 'PathName' in MyDriver: full_filename = os.path.join(MyDriver['PathName'], filename) fdo = open(full_filename, 'w') put_preamble(MyDriver) put_standard_code(MyDriver) put_mkDriver(MyDriver) put_postamble(MyDriver) put_read_config(MyDriver) put_check_config(MyDriver) fdo.close() if CodeDump or Verbose: print "Code Fragments:", MyDriver['Funcs'] for f in sorted(MyDriver['Funcs'].keys()): print "Function:", f, "Type:", MyDriver['Funcs'][f]['type'], '#Uses:', MyDriver['Funcs'][f]['reference_count'] for l in MyDriver['Funcs'][f]['text']: print " ", l print "Produced file %s with %d lines." % (filename, NumberOfLinesOut) def process_drivers(TheDrivers): if Verbose: print "TheDrivers:", TheDrivers for driver in TheDrivers: MyDriver = {'name':driver} MyDriver['namespace'] = '::scobj::%s' % driver MyDriver['debug_threshold'] = '5' MyDriver['Groups'] = {} MyDriver['Funcs'] = {} MyDriver['Permlink'] = {} MyDriver['Deferred'] = [] build_driver(MyDriver, TheDrivers[driver]) if Verbose: print "MyDriver:", MyDriver['name'], '=', MyDriver if DriverDump or Verbose: dump_driver(MyDriver) generate_driver(MyDriver) def process_source(source_files): global PathName global TheDrivers global NumberOfLinesIn TheDrivers = {} # # Build the lexer # lexer = lex.lex() # # Build the parser # #yaccer = yacc.yacc(tabmodule="gen_sct",outputdir="/tmp",write_tables=0,debug=0) yaccer = yacc.yacc() for source_file in source_files: PathName = os.path.realpath(os.path.abspath(os.path.dirname(source_file))) fd = open(source_file, 'r') data = fd.read() fd.close() NumberOfLinesIn = data.count('\n') start_line = lexer.lineno yaccer.parse(data) stop_line = lexer.lineno print 'Consumed file %s with %d lines (%d, %d)' % (source_file, NumberOfLinesIn, start_line, stop_line - 1) lexer.lineno = 1 process_drivers(TheDrivers) def main(): global Verbose global DriverDump global CodeDump import argparse parser = argparse.ArgumentParser() parser.add_argument("-c", "--code", help="dump code", action="store_true") parser.add_argument("-d", "--driver", help="dump driver", action="store_true") parser.add_argument("-v", "--verbose", help="verbose output", action="store_true") parser.add_argument("driver_source", help="driver source file", nargs="*") args = parser.parse_args() print args if args.code: CodeDump = True else: CodeDump = False if args.driver: DriverDump = True else: DriverDump = False if args.verbose: Verbose = True else: Verbose = False source_files = args.driver_source if source_files: process_source(source_files) if __name__ == "__main__": main()