source: sasview/sansmodels/src/sans/models/c_extensions/WrapperGenerator.py @ 89fef2c

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 89fef2c was 4d3acb6, checked in by Gervaise Alina <gervyh@…>, 16 years ago

description added

  • Property mode set to 100644
File size: 14.1 KB
Line 
1#!/usr/bin/env python
2""" WrapperGenerator class to generate model code automatically.
3"""
4
5import os, sys
6import re
7
8class WrapperGenerator:
9    """ Python wrapper generator for C models
10   
11        The developer must provide a header file describing
12        the new model.
13       
14        To provide the name of the Python class to be
15        generated, the .h file must contain the following
16        string in the comments:
17       
18        // [PYTHONCLASS] = my_model
19       
20        where my_model must be replaced by the name of the
21        class that you want to import from sans.models.
22        (example: [PYTHONCLASS] = MyModel
23          will create a class MyModel in sans.models.MyModel.
24          It will also create a class CMyModel in
25          sans_extension.c_models.)
26         
27        Also in comments, each parameter of the params
28        dictionary must be declared with a default value
29        in the following way:
30       
31        // [DEFAULT]=param_name=default_value
32       
33        (example:
34            //  [DEFAULT]=radius=20.0
35        )
36         
37        See cylinder.h for an example.
38       
39       
40        A .c file corresponding to the .h file should also
41        be provided (example: my_model.h, my_model.c).
42   
43        The .h file should define two function definitions. For example,
44        cylinder.h defines the following:
45       
46            /// 1D scattering function
47            double cylinder_analytical_1D(CylinderParameters *pars, double q);
48           
49            /// 2D scattering function
50            double cylinder_analytical_2D(CylinderParameters *pars, double q, double phi);
51           
52        The .c file implements those functions.
53       
54        @author: Mathieu Doucet / UTK
55        @contact: mathieu.doucet@nist.gov
56    """
57   
58    def __init__(self, filename):
59        """ Initialization """
60       
61        ## Name of .h file to generate wrapper from
62        self.file = filename
63       
64        # Info read from file
65       
66        ## Name of python class to write
67        self.pythonClass = None
68        ## Parser in struct section
69        self.inStruct = False
70        ## Name of struct for the c object
71        self.structName = None
72        ## Dictionary of parameters
73        self.params = {}
74        ## ModelCalculation module flag
75        self.modelCalcFlag = False
76        ## List of default parameters (text)
77        self.default_list = ""
78        ##description
79        self.description=''
80        ## Dictionary of units
81        self.details = ""
82       
83    def __repr__(self):
84        """ Simple output for printing """
85       
86        rep  = "Python class: %s\n" % self.pythonClass
87        rep += "  struc name: %s\n" % self.structName
88        rep += "  params:     %s\n" % self.params
89        rep += "  description: %s\n"% self.description
90        return rep
91       
92    def read(self):
93        """ Reads in the .h file to catch parameters of the wrapper """
94       
95        # Check if the file is there
96        if not os.path.isfile(self.file):
97            raise ValueError, "File %s is not a regular file" % self.file
98       
99        # Read file
100        f = open(self.file,'r')
101        buf = f.read()
102       
103        self.default_list = "List of default parameters:\n"
104        #lines = string.split(buf,'\n')
105        lines = buf.split('\n')
106        self.details  = "## Parameter details [units, min, max]\n"
107        self.details += "        self.details = {}\n"
108        # Catch Description
109        key = "[DESCRIPTION]"
110        find_description= 0
111        temp=""
112        for line in lines:
113            if line.count(key)>0 :
114               
115                try:
116                    find_description= 1
117                    index = line.index(key)
118                    toks = line[index:].split("=",1 )
119                    temp=toks[1].lstrip().rstrip()
120                    text='text'
121                    key2="<%s>"%text.lower()
122                    if re.match(key2,temp)!=None:
123                        #index2 = line.index(key2)
124                        #temp = temp[index2:]
125                        toks2=temp.split(key2,1)
126                        self.description=toks2[1]
127                        text='text'
128                        key2="</%s>"%text.lower()
129                        if re.search(key2,toks2[1])!=None:
130                            temp=toks2[1].split(key2,1)
131                            self.description=temp[0]
132                            break
133                        print self.description
134                    else:
135                        self.description=temp
136                        break
137                except:
138                     raise ValueError, "Could not parse file %s" % self.file
139            elif find_description==1:
140                text='text'
141                key2="</%s>"%text.lower()
142                #print "second line",line,key2,re.search(key2,line)
143                if re.search(key2,line)!=None:
144                    tok=line.split(key2,1)
145                    temp=tok[0].split("//",1)
146                    self.description+=tok[1].lstrip().rstrip()
147                    break
148                else:
149                    if re.search("*",line)!=None:
150                        temp=line.split("//",1)
151                        self.description+='\n'+temp[1].lstrip().rstrip()
152                    if re.search("//",line)!=None:
153                        temp=line.split("//",1)
154                        self.description+='\n'+temp[1].lstrip().rstrip()
155                       
156                    else:
157                        self.description+='\n'+line.lstrip().rstrip()
158                   
159               
160        for line in lines:
161           
162            # Catch class name
163            key = "[PYTHONCLASS]"
164            if line.count(key)>0:
165                try:
166                    index = line.index(key)
167                    #toks = string.split( line[index:], "=" )
168                    toks = line[index:].split("=" )
169                    self.pythonClass = toks[1].lstrip().rstrip()
170                except:
171                    raise ValueError, "Could not parse file %s" % self.file
172               
173            # Catch struct name
174            if line.count("typedef struct")>0:
175                # We are entering a struct block
176                self.inStruct = True
177           
178            if self.inStruct and line.count("}")>0:
179                # We are exiting a struct block
180                self.inStruct = False
181   
182                # Catch the name of the struct
183                index = line.index("}")
184                #toks = string.split(line[index+1:],";")
185                toks = line[index+1:].split(";")
186                # Catch pointer definition
187                #toks2 = string.split(toks[0],',')
188                toks2 = toks[0].split(',')
189                self.structName = toks2[0].lstrip().rstrip()
190         
191            # Catch struct content
192            key = "[DEFAULT]"
193            if self.inStruct and line.count(key)>0:
194                # Found a new parameter
195                try:
196                    index = line.index(key)
197                    toks = line[index:].split("=")
198                    toks2 = toks[2].split()
199                    val = float(toks2[0])
200                    self.params[toks[1]] = val
201                    #self.pythonClass = toks[1].lstrip().rstrip()
202                    units = ""
203                    if len(toks2) >= 2:
204                        units = toks2[1]
205                    self.default_list += "         %-15s = %s %s\n" % \
206                        (toks[1], val, units)
207                   
208                    # Check for min and max
209                    min = "None"
210                    max = "None"
211                    if len(toks2) == 4:
212                        min = toks2[2]
213                        max = toks2[3]
214                   
215                    self.details += "        self.details['%s'] = ['%s', %s, %s]\n" % \
216                        (toks[1].lstrip().rstrip(), units.lstrip().rstrip(), min, max)
217                except:
218                    raise ValueError, "Could not parse input file %s \n  %s" % \
219                        (self.file, sys.exc_value)
220               
221               
222            # Catch need for numerical calculations
223            key = "CalcParameters calcPars"
224            if line.count(key)>0:
225                self.modelCalcFlag = True
226               
227               
228               
229    def write_c_wrapper(self):
230        """ Writes the C file to create the python extension class
231            The file is written in C[PYTHONCLASS].c
232        """
233       
234        file = open("C"+self.pythonClass+'.c', 'w')
235        template = open("classTemplate.txt", 'r')
236       
237        tmp_buf = template.read()
238        #tmp_lines = string.split(tmp_buf,'\n')
239        tmp_lines = tmp_buf.split('\n')
240       
241        for tmp_line in tmp_lines:
242           
243            # Catch class name
244            newline = self.replaceToken(tmp_line, 
245                                        "[PYTHONCLASS]", 'C'+self.pythonClass)
246           
247            # Catch class name
248            newline = self.replaceToken(newline, 
249                                        "[MODELSTRUCT]", self.structName)
250           
251            # Dictionary initialization
252            param_str = "// Initialize parameter dictionary\n"
253            for par in self.params:
254                param_str += "        PyDict_SetItemString(self->params,\"%s\",Py_BuildValue(\"d\",%f));\n" % \
255                    (par, self.params[par])
256               
257            newline = self.replaceToken(newline,
258                                        "[INITDICTIONARY]", param_str)
259           
260            # Read dictionary
261            param_str = "// Reader parameter dictionary\n"
262            for par in self.params:
263                param_str += "    self->model_pars.%s = PyFloat_AsDouble( PyDict_GetItemString(self->params, \"%s\") );\n" % \
264                    (par, par)
265               
266            newline = self.replaceToken(newline, "[READDICTIONARY]", param_str)
267               
268            # Name of .c file
269            #toks = string.split(self.file,'.')
270            toks = self.file.split('.')
271            newline = self.replaceToken(newline, "[C_FILENAME]", toks[0])
272           
273            # Include file
274            newline = self.replaceToken(newline, 
275                                        "[INCLUDE_FILE]", self.file)           
276               
277            # Numerical calcs dealloc
278            dealloc_str = "\n"
279            if self.modelCalcFlag:
280                dealloc_str = "    modelcalculations_dealloc(&(self->model_pars.calcPars));\n"
281            newline = self.replaceToken(newline, 
282                                        "[NUMERICAL_DEALLOC]", dealloc_str)     
283               
284            # Numerical calcs init
285            init_str = "\n"
286            if self.modelCalcFlag:
287                init_str = "        modelcalculations_init(&(self->model_pars.calcPars));\n"
288            newline = self.replaceToken(newline, 
289                                        "[NUMERICAL_INIT]", init_str)     
290               
291            # Numerical calcs reset
292            reset_str = "\n"
293            if self.modelCalcFlag:
294                reset_str = "modelcalculations_reset(&(self->model_pars.calcPars));\n"
295            newline = self.replaceToken(newline, 
296                                        "[NUMERICAL_RESET]", reset_str)     
297               
298            # Write new line to the wrapper .c file
299            file.write(newline+'\n')
300           
301           
302        file.close()
303       
304    def write_python_wrapper(self):
305        """ Writes the python file to create the python extension class
306            The file is written in ../[PYTHONCLASS].py
307        """
308       
309        file = open("../"+self.pythonClass+'.py', 'w')
310        template = open("modelTemplate.txt", 'r')
311       
312        tmp_buf = template.read()
313        tmp_lines = tmp_buf.split('\n')
314       
315        for tmp_line in tmp_lines:
316           
317            # Catch class name
318            newline = self.replaceToken(tmp_line, 
319                                        "[CPYTHONCLASS]", 'C'+self.pythonClass)
320           
321            # Catch class name
322            newline = self.replaceToken(newline, 
323                                        "[PYTHONCLASS]", self.pythonClass)
324           
325            # Include file
326            newline = self.replaceToken(newline, 
327                                        "[INCLUDE_FILE]", self.file)   
328                   
329            # Include file
330            newline = self.replaceToken(newline, 
331                                        "[DEFAULT_LIST]", self.default_list)
332            # Model Description
333            newline = self.replaceToken(newline, 
334                                        "[DESCRIPTION]", self.description)
335            # Parameter details
336            newline = self.replaceToken(newline, 
337                                        "[PAR_DETAILS]", self.details)
338
339            # Write new line to the wrapper .c file
340            file.write(newline+'\n')
341               
342        file.close()
343       
344       
345    def replaceToken(self, line, key, value): #pylint: disable-msg=R0201
346        """ Replace a token in the template file
347            @param line: line of text to inspect
348            @param key: token to look for
349            @param value: string value to replace the token with
350            @return: new string value
351        """
352        lenkey = len(key)
353        newline = line
354        while newline.count(key)>0:
355            index = newline.index(key)
356            newline = newline[:index]+value+newline[index+lenkey:]
357        return newline
358       
359       
360# main
361if __name__ == '__main__':
362    if len(sys.argv)>1:
363        print "Will look for file %s" % sys.argv[1]
364        app = WrapperGenerator(sys.argv[1])
365    else:
366        app = WrapperGenerator("test.h")
367    app.read()
368    app.write_c_wrapper()
369    app.write_python_wrapper()
370    print app
371   
372# End of file       
Note: See TracBrowser for help on using the repository browser.