source: topdoc/src/TopDoc/EpicsDatabase.py @ 541

Last change on this file since 541 was 541, checked in by jemian, 12 years ago

account for path differences

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Date Revision Author HeadURL Id
File size: 14.6 KB
Line 
1#!/usr/bin/env python
2
3'''
4Read an EPICS .db database file into memory.
5'''
6
7
8########### SVN repository information ###################
9# $Date: 2011-07-14 16:18:18 +0000 (Thu, 14 Jul 2011) $
10# $Author: jemian $
11# $Revision: 541 $
12# $URL: topdoc/src/TopDoc/EpicsDatabase.py $
13# $Id: EpicsDatabase.py 541 2011-07-14 16:18:18Z jemian $
14########### SVN repository information ###################
15
16
17import os
18import pprint
19import utilities
20import TokenLog
21
22
23class Db():
24    '''
25    Read an EPICS .db database file into memory.
26    The internal representation of the file is in self.pvDict.
27    This is a Python dictionary of dictionaries.  Each outer dictionary
28    item is for a specific instance of an EPICS process variable (PV).
29    The internal dictionary keys are field names and the dictionary values
30    are the field values before macro expansion.
31
32    Important methods provided are:
33   
34    ==================  ====================
35    method              description
36    ==================  ====================
37    :func:`countPVs`    number of PVs that have been defined
38    :func:`getPVs`      Get a list of the EPICS PVs defined with optional macro substitutions.
39    :func:`getFull`     Get the full specification of the PVs and fields with optional macro substitutions.
40    :func:`keyName`     Find the dictionary key that expands to pvName.
41    :func:`xrefDict`    Cross-references the 'NAME' field  between defined and macro-expanded values.
42    ==================  ====================
43
44    From the EPICS Application Developer's Guide
45
46    :see: http://www.aps.anl.gov/epics/base/R3-14/12-docs/AppDevGuide/node7.html
47
48        6.3.2 Unquoted Strings
49       
50        In the summary section, some values are shown as quoted strings and some unquoted.
51        The actual rule is that any string consisting of only the following characters
52        does not have to be quoted unless it contains one of the above keywords:
53       
54        ::
55   
56            a-z A-Z 0-9 _ -- : . [ ] < > ;
57       
58        ::
59   
60            my regexp:  [a-zA-Z0-9_-:.\[\]<>;]   (What about these? -- [ ])
61       
62        These are also the legal characters for process variable names.
63        Thus in many cases quotes are not needed.
64       
65        6.3.3 Quoted Strings
66       
67        A quoted string can contain any ascii character except the quote character ".
68        The quote character itself can given by using \ as an escape.
69        For example "\"" is a quoted string containing the single character ".
70       
71        6.3.4 Macro Substitution
72       
73        Macro substitutions are permitted inside quoted strings.
74        Macro instances take the form:
75       
76        ::
77   
78            $(name)
79       
80        or
81       
82        ::
83   
84            ${name}
85       
86        There is no distinction between the use of parentheses or braces
87        for delimiters, although the two must match for a given macro instance.
88        The macro name can be made up from other macros, for example:
89       
90        ::
91   
92            $(name_$(sel))
93       
94        A macro instance can also provide a default value that is used when no
95        macro with the given name is defined. The default value can be defined
96        in terms of other macros if desired, but cannot contain any unescaped
97        comma characters. The syntax for specifying a default value is as follows:
98       
99        ::
100   
101            $(name=default)
102       
103        Finally macro instances can also contain definitions of other macros,
104        which can (temporarily) override any existing values for those macros
105        but are in scope only for the duration of the expansion of this macro
106        instance. These definitions consist of name=value sequences separated
107        by commas, for example:
108       
109        ::
110       
111            $(abcd=$(a)$(b)$(c)$(d),a=A,b=B,c=C,d=D)
112   
113        ...
114   
115        6.14 record - Record Instance
116       
117        6.14.1 Format
118       
119        ::
120   
121            record(record_type, record_name) {
122                include "filename"
123                alias(alias_name)
124                field(field_name, "field_value")
125                info(info_name, "info_value")
126                ...
127            }
128            alias(record_name, alias_name)
129       
130        MACRO DEFINITION
131       
132        ::
133       
134            $(name)                                    typical
135            ${name}                                    alternate
136            $(name_$(sel))                             embedded
137            $(name=default)                            default value if not defined
138            $(abcd=$(a)$(b)$(c)$(d),a=A,b=B,c=C,d=D)   define other macros
139    '''
140
141    def __init__(self, dbFilename):
142        '''
143        Constructor.  Try to read and interpret the named file.
144
145        :param dbFilename: string with name of .db file to open
146        '''
147        self.filename = None
148        self.absolute_filename = None
149        self.buf = None
150        self.pvDict = {}
151        self.cache_xref = None
152        self.cache_macros = None
153        if os.path.exists(dbFilename):
154            self.filename = dbFilename
155            self.absolute_filename = os.path.abspath(dbFilename)
156            self.tokenLog = TokenLog.TokenLog()
157            self.tokenLog.processFile(dbFilename)
158            self.buf = self.tokenLog.getTokenList()
159            self.interpret()
160
161    def xrefDict(self, macros = {}):
162        '''
163        Make a dictionary that cross-references the 'NAME' field
164        between defined and macro-expanded values.  Both forward
165        and reverse definitions are included.
166
167        :param macros: dictionary of macro substitutions.
168        :return: cross-reference dictionary
169        '''
170        # make this more efficient for repeated calls by caching the xref
171        if self.cache_macros == macros:
172            return self.cache_xref
173        xref = {}
174        for k, v in self.pvDict.items():
175            name = utilities.replaceMacros( v['NAME'], macros )
176            xref[k] = name
177            xref[name] = k
178        self.cache_macros = macros
179        self.cache_xref = xref
180        return xref
181
182    def keyName(self, pvName, macros = {}):
183        '''
184        Find the dictionary key that expands to pvName.
185        This is easy to get from the cross-reference dictionary.
186        (This actually works either way since it uses the xrefDict() method.)
187
188        :param macros: dictionary of macro substitutions.
189        :return: name of dictionary key or None if not found
190        '''
191        try:
192            return self.xrefDict(macros)[pvName]
193        except:
194            return None
195
196    def countPVs(self):
197        '''
198        :return: number of PVs that have been defined
199        '''
200        return len(self.pvDict)
201
202    def getPVs(self, macros = {}):
203        '''
204        Get a list of the EPICS PVs defined with optional macro substitutions.
205        The result with no macro substitutions is trivial.
206
207        :param macros: dictionary of macro substitutions.
208        :return: list PV names or None if no PVs records have been defined
209        '''
210        keys = self.pvDict.keys()
211        result = [utilities.replaceMacros( item, macros ) for item in keys]
212        return result
213
214    def getFull(self, macros = {}):
215        '''
216        Get the full specification of the PVs and fields with optional macro substitutions.
217        The result with no macro substitutions is trivial.
218
219        :param macros: dictionary of macro substitutions. Keys are expanded, also.
220        :return: dictionary of dictionaries
221        '''
222        result = {}
223        for key, fieldDict in self.pvDict.items():
224            k = utilities.replaceMacros( key, macros )
225            result[k] = {}
226            for field, value in fieldDict.items():
227                v = utilities.replaceMacros( value, macros )
228                result[k][field] = utilities.replaceMacros( v, macros )
229        return result
230
231    def interpret(self):
232        '''
233        Interpret the contents of the .db file
234        into the internal memory structure.
235       
236        :raise Exception:
237        '''
238        pvDict = {}
239        tkn = self.tokenLog.nextActionable()
240        while tkn is not None:
241            '''
242            :see: http://www.aps.anl.gov/epics/base/R3-14/12-docs/AppDevGuide/node7.html
243           
244            The Following defines a Record Instance::
245           
246                record(record_type,record_name) {
247                include "filename"
248                field(field_name,"value")
249                    alias(alias_name)
250                    info(info_name,"value")
251                    ...
252                }
253                alias(record_name,alias_name)
254            '''
255            # FIXME:  grecord(longin, "$(P)eps:V3104")       { field(DESC, "saved VC0") }
256            if tkn['tokName'] == 'NAME' and tkn['tokStr'] in ('record', 'grecord'):
257                # start of record declaration
258                tkn = self.tokenLog.nextActionable() # token with "(" character
259                rtyp, name, tkn = self.getTwoItems(tkn)
260                fieldDict = {
261                    'RTYP': rtyp,    # record type
262                    'NAME': name     # record name
263                }
264                # trap case where there are NO field declarations
265                if not (tkn == None or tkn['tokName'] == 'NAME' and tkn['tokStr'] in ('record', 'grecord')):
266                    tkn = self.tokenLog.nextActionable() # load the next token
267                    while tkn['tokStr'] != "}":
268                        if tkn['tokName'] == 'NAME':
269                            if tkn['tokStr'] in ('field'):
270                                # TODO: support 'info' and 'alias' in addition to 'field'
271                                tkn = self.tokenLog.nextActionable() # "(" character
272                                argText = tkn['tokLine'].strip()[len('field'):]
273                                argText = utilities.strip_outer_pair(argText, '(', ')')
274                                pos = argText.find(",")
275                                if pos >= 0 and pos < len(argText):
276                                    field = argText[:pos]
277                                    value = utilities.strip_quotes( argText[pos+1:].strip() )
278                                    fieldDict[field] = value
279                                    tkn = self.advanceToNewLine()
280                                    tkn = self.tokenLog.nextActionable()
281                                else:
282                                    Exception, "Could not handle this case: " + tkn['tokLine']
283                            elif tkn['tokStr'] in ('alias', 'include', 'info'):
284                                utilities.logMessage(self.absolute_filename)
285                                utilities.logMessage("line %d" % tkn['start'][0])
286                                msg = "ignoring: %s" % tkn['tokLine'].strip("\n")
287                                utilities.logMessage(msg)
288                                # skip over this one
289                                # TODO: this fails for an inline case
290                                # grecord(longin, "$(P)eps:V3104")       { field(DESC, "saved VC0") }
291                                while tkn['tokName'] != 'NEWLINE':
292                                    tkn = self.tokenLog.next()
293                                tkn = self.tokenLog.nextActionable()
294                            else:
295                                utilities.logMessage(self.absolute_filename)
296                                utilities.logMessage("line %d" % tkn['start'][0])
297                                msg = "unknown: %s" % tkn['tokLine'].strip("\n")
298                                raise Exception, msg
299                        else:
300                            utilities.logMessage(self.absolute_filename)
301                            utilities.logMessage("line %d" % tkn['start'][0])
302                            for k in sorted(fieldDict):
303                                utilities.logMessage("   fieldDict[%s] = %s" % (k, str(fieldDict[k])))
304                            msg = "did not find field: %s" % str(tkn)
305                            raise Exception, msg
306                    tkn = self.tokenLog.nextActionable()
307                pvDict[name] = fieldDict
308            else:
309                linenum = tkn['start'][0]
310                msg = "(%s,%d) did not find record: %s" % (self.absolute_filename, linenum, str(tkn))
311                raise Exception, msg   
312        self.pvDict = pvDict
313
314    def advanceToNewLine(self):
315        '''
316        Move the token pointer to the next NEWLINE token
317
318        :return: token after the next NEWLINE token
319        '''
320        tkn = self.tokenLog.next()
321        while tkn['tokName'] != 'NEWLINE':
322            tkn = self.tokenLog.next()
323        return tkn
324
325    def getTwoItems(self, tkn):
326        '''
327        Read a structure of (OP, some:thing-like_this0)
328
329        :param tkn: token
330        :return: tuple of (word1, word2, tkn)
331        '''
332        tkn = self.tokenLog.nextActionable() # load the next token
333        word1 = utilities.strip_quotes( tkn['tokStr'] )  # 1st item always 1 token
334        tkn = self.tokenLog.nextActionable()             # 2nd item might be many tokens
335        word2, tkn = self.gatherUpToCloseParen(tkn)
336        return word1, word2, tkn
337
338    def gatherUpToCloseParen(self, tkn):
339        '''
340        Read tokens and accumulate until a ")" character is found
341
342        :param tkn: token
343        :return: tuple of (word, tkn)
344        '''
345        word = ""
346        while tkn['tokStr'] != ")":
347            # could also watch for breaks in the line position
348            if tkn['tokStr'] not in (",", "'", '"'):
349                word += tkn['tokStr']
350            tkn = self.tokenLog.nextActionable()
351        # next token after ")"
352        tkn = self.tokenLog.nextActionable()
353        return utilities.strip_quotes( word ), tkn
354
355
356if __name__ == '__main__':
357    dbFilename = "../../IOCs/12id/12ida1App/Db/SB_DCM.db"
358    macros = {'P': '12ida1:', 'MTH1': 'm9', 'MTH2': 'm11', 'MZ2': 'm14'}
359    # ....
360    dbFilename = "../../IOCs/12id/iocBoot/ioc12ida1/test.db"
361    macros = {'P': 'prj:', 
362              'S': 's', 
363              'DTYP': 'Asyn Scaler', 
364              'FREQ': '50000000', 
365              'OUT': '@asyn(mcaSIS3820/1 0)'}
366    #dbFilename = "C:\\Users\\Pete\\Apps\\CSS_SoftIOC\\demo\\Ctrl_PID.vdb"
367    #macros = {}
368    # ....
369    db = Db(dbFilename)
370    print "Unexpanded list of PVs:"
371    for item in sorted(db.getPVs()):
372        print item
373    print "Expanded list of PVs:"
374    for item in sorted(db.getPVs({'P': '12ida1:'})):
375        print item
376    print 'filename:', db.filename
377    print 'absolute filename on this system:', db.absolute_filename
378    print 'There are %d PV records defined' % db.countPVs()
379    print "Fully expanded list of PVs and fields:"
380    pprint.pprint( db.getFull(macros) )
381    print "%s expanded to %s" % (db.keyName('12ida1:DCM:LM2', macros), '12ida1:DCM:LM2')
382    print "Cross-reference dictionary:"
383    pprint.pprint( db.xrefDict(macros) )
Note: See TracBrowser for help on using the repository browser.