]> git.tdb.fi Git - gldbg.git/blob - generate.py
Add an errata system to avoid the need of putting special cases in code
[gldbg.git] / generate.py
1 #!/usr/bin/python
2 # $Id$
3
4 import sys
5 import os
6 import optparse
7
8 class InputFile:
9         def __init__(self, fn):
10                 self.file = open(fn)
11
12         def __iter__(self):
13                 for l in self.file:
14                         h = l.find("#")
15                         if h==0 or (h>0 and l[h-1].isspace()):
16                                 l = l[:h]
17
18                         l = l.rstrip()
19                         if not l:
20                                 continue
21
22                         yield l
23                 
24
25 def strip_name(name):
26         """Strips any vendor suffix and GL prefix from a name (but not GLX prefix)"""
27
28         suffix = ""
29         if name.endswith(" *"):
30                 suffix = " *"
31                 name = name[:-2]
32         elif name.endswith("Pointer"):
33                 suffix = "Pointer"
34                 name = name[:-7]
35
36         prefix = ""
37         if name.startswith("const "):
38                 prefix = "const "
39                 name = name[6:]
40
41         if name.startswith("GL") and not name.startswith("GLX"):
42                 name = name[2:]
43         if name.endswith(("EXT", "ARB", "SGI", "IBM", "ATI")):
44                 return prefix+name[:-3]+suffix
45         elif name.endswith("SGIX"):
46                 return prefix+name[:-4]+suffix
47         elif name.endswith(("NV", "HP")):
48                 return prefix+name[:-2]+suffix
49         else:
50                 return prefix+name+suffix
51
52
53 class Typemap:
54         def __init__(self, fn):
55                 self.map = {}
56                 for line in InputFile(fn):
57                         parts = [p.strip() for p in line.split(',')]
58                         if parts[3]=="*":
59                                 parts[3] = parts[0]
60                         elif parts[3][-1]=='*' and parts[3][-2]!=' ':
61                                 parts[3] = parts[3][:-1]+" *"
62                         self.map[tuple(parts[0:3])] = tuple(parts[3:6])
63
64         def wildcard_match(self, a, b):
65                 if a=="*" or b=="*":
66                         return True
67                 return a==b
68
69         def __getitem__(self, key):
70                 try:
71                         return self.map[(key[0], "*", "*")]
72                 except KeyError:
73                         for k, v in self.map.iteritems():
74                                 if strip_name(k[0])==strip_name(key[0]) and self.wildcard_match(k[1], key[1]) and self.wildcard_match(k[2], key[2]):
75                                         return v
76                         raise KeyError, key
77
78         def update(self, other):
79                 self.map.update(other.map)
80
81
82 class IOmap:
83         def __init__(self, fn):
84                 self.map = {}
85                 self.map["void"] = None
86                 for line in InputFile(fn):
87                         parts = [p.strip() for p in line.split(',')]
88                         self.map[parts[0]] = tuple(parts[1:])
89
90         def __getitem__(self, key):
91                 return self.map[strip_name(key)]
92
93
94 class Function:
95         class Parameter:
96                 def __init__(self, func, name):
97                         self.func = func
98                         self.name = name
99                         self.type = None
100                         self.direction = "in"
101                         self.kind = "value"
102                         self.size = None
103                         self.ctype = None
104                         self.csize = None
105                         self.io = None
106
107                 def set_type(self, type, dir, kind):
108                         self.type = type
109                         self.direction = dir
110                         self.kind = kind
111
112                 def set_size(self, size):
113                         if type(size)==str and size.isdigit():
114                                 self.size = int(size)
115                         else:
116                                 self.size = size
117
118                 def derive_ctype(self, typemap, iomap):
119                         m = typemap[(self.type, self.direction, self.kind)]
120                         self.ctype = m[0]
121                         if m[1]!="*":
122                                 self.direction = m[1]
123                         if m[2]!="*":
124                                 self.kind = m[2]
125                         self.base_ctype = self.ctype
126                         if self.kind=="value":
127                                 if self.base_ctype.startswith("const "):
128                                         self.base_ctype = self.base_ctype[6:]
129                         else:
130                                 if self.direction=="in":
131                                         self.ctype = "const "+self.ctype
132                                 self.ctype = self.ctype+" *"
133                         self.io = iomap[self.base_ctype]
134
135                 def derive_csize(self):
136                         if self.kind=="array" and self.size is not None:
137                                 self.csize = None
138                                 if type(self.size)==int:
139                                         self.csize = "%d*sizeof(%s)"%(self.size, self.base_ctype)
140                                 elif self.size.startswith("COMPSIZE("):
141                                         self.csize = self.func.compsize(self.size[9:-1], self.base_ctype)
142                                 elif self.size=="pname":
143                                         self.csize = "paramsize(pname)*sizeof(%s)"%self.base_ctype
144                                 else:
145                                         s = self.func.get_param(self.size.split('*')[0])
146                                         if (s.type=="SizeI" or s.type.endswith("Int32") or s.type.startswith("BufferSize")):
147                                                 if s.kind=="value":
148                                                         self.csize = "%s"%self.size
149                                                         if strip_name(self.base_ctype)!="void":
150                                                                 self.csize += "*sizeof(%s)"%self.base_ctype
151                                                 elif s.kind=="array" and s.size==1:
152                                                         self.csize = "*%s"%s.name
153                                         if not self.csize:
154                                                 sys.stderr.write("Could not determine size for array parameter '%s[%s]' of function '%s'\n"%(self.name, self.size, self.func.name))
155                         elif self.kind=="reference":
156                                 self.csize = "sizeof(%s)"%self.base_ctype
157
158         def __init__(self, name, pnames):
159                 self.name = name
160                 self.ret = Function.Parameter(self, "ret")
161                 self.params = [Function.Parameter(self, n) for n in pnames]
162                 self.category = None
163
164         def get_param(self, pname):
165                 for p in self.params:
166                         if p.name==pname:
167                                 return p
168                 raise KeyError, pname
169
170         def set_category(self, cat):
171                 self.category = cat
172
173         def compsize(self, size, btype):
174                 if not size:
175                         return
176
177                 res = ""
178                 have_type = False
179                 for c in size.replace(',', '/').split('/'):
180                         param = self.get_param(c)
181                         if not param:
182                                 sys.stderr.write("Compsize '%s' for function '%s' failed: No parameter '%s'\n"%(size, self.name, c))
183                                 return
184
185                         if res:
186                                 res += "*"
187
188                         cn = strip_name(param.type)
189                         if cn.endswith("Type"):
190                                 res += "typesize(%s)"%param.name
191                                 have_type = True
192                         elif cn.endswith("Format"):
193                                 res += "formatsize(%s)"%param.name
194                         elif param.name=="pname" or cn.endswith("Parameter") or (param.name=="target" and cn=="enum"):
195                                 res += "paramsize(%s)"%param.name
196                         elif param.name=="buffer" and cn=="enum":
197                                 res += "buffersize(%s)"%param.name
198                         elif (cn=="SizeI" or cn.endswith("Int32")) and not param.size:
199                                 res += param.name
200                         else:
201                                 sys.stderr.write("Compsize '%s' for function '%s' failed: Parameter '%s' has unknown type '%s'\n"%(size, self.name, param.name, param.type))
202                                 return
203                 if not have_type:
204                         res += "*sizeof(%s)"%btype
205                 return res
206
207         def finalize(self, typemap, iomap):
208                 self.ret.derive_ctype(typemap, iomap)
209                 for p in self.params:
210                         p.derive_ctype(typemap, iomap)
211                 for p in self.params:
212                         p.derive_csize()
213
214
215 class Enum:
216         def __init__(self, name, category, value):
217                 self.name = name
218                 self.category = category
219                 self.value = value
220
221
222 class Template:
223         def __init__(self, fn):
224                 self.mode = "functions"
225                 self.sections = []
226                 self.handcode = []
227
228                 literal = True
229                 text = ""
230                 for line in InputFile(fn):
231                         if line[0]==':':
232                                 if not literal and text:
233                                         self.add_section(text, literal)
234                                         text = ""
235                                 text += line[1:]+"\n"
236                                 literal = True
237                         elif line[0]=='!':
238                                 parts = line[1:].split()
239                                 if parts[0]=="handcode":
240                                         self.handcode.append(parts[1])
241                                 elif parts[0]=="mode":
242                                         self.mode = parts[1]
243                                 else:
244                                         sys.stderr.write("Unknown keyword '%s'\n"%parts[0])
245                         else:
246                                 if literal and text:
247                                         self.add_section(text, literal)
248                                         text = ""
249                                 text += line+"\n"
250                                 literal = False
251                 if text:
252                         self.add_section(text, literal)
253
254         def add_section(self, text, literal):
255                 if literal:
256                         self.sections.append(text)
257                 else:
258                         self.sections.append(compile(text, "-", "exec"))
259
260         def write(self, str, *args):
261                 sys.stdout.write(str%args)
262
263         def writeln(self, str, *args):
264                 sys.stdout.write(str%args+"\n")
265
266         def process_function(self, code, func):
267                 if func.name in self.handcode:
268                         return
269
270                 globals = {
271                         "w": self.write,
272                         "wl": self.writeln,
273                         "func": func,
274                         "ret": func.ret,
275                         "params": func.params
276                 }
277                 eval(code, globals)
278
279         def process_enum(self, code, enum):
280                 if enum.value is None:
281                         return
282
283                 globals = {
284                         "w": self.write,
285                         "wl": self.writeln,
286                         "enum": enum
287                 }
288                 eval(code, globals)
289
290         def process_sections(self, objects, handler):
291                 for sect in self.sections:
292                         if type(sect)==str:
293                                 print sect
294                         else:
295                                 for obj in objects:
296                                         handler(sect, obj)
297
298         def process(self, apis):
299                 if self.mode=="functions":
300                         functions = []
301                         for api in apis:
302                                 typemap = Typemap(api.typemap)
303                                 iomap = IOmap(api.iomap)
304                                 for spec in api.specs:
305                                         funcs = read_spec(spec, api.prefix)
306                                         for err in api.errata:
307                                                 read_spec(err, api.prefix, funcs)
308                                         funcs = [f for f in funcs if f.name not in api.ignore_funcs and f.category not in api.ignore_categs]
309                                         for func in funcs:
310                                                 func.finalize(typemap, iomap)
311                                         names = [f.name for f in funcs]
312                                         functions = [f for f in functions if f.name not in names]+funcs
313                         self.process_sections(functions, self.process_function)
314                 elif self.mode=="enums":
315                         enums = []
316                         for api in apis:
317                                 for spec in api.enumspecs:
318                                         ens = read_enums(spec, api.enumprefix)
319                                         enums += ens
320                         enums.sort(lambda x, y: cmp(x.value, y.value)*2+cmp(x.category, y.category))
321                         self.process_sections(enums, self.process_enum)
322
323
324 class Api:
325         def __init__(self, fn):
326                 self.typemap = None
327                 self.iomap = None
328                 self.specs = []
329                 self.errata = []
330                 self.prefix = None
331                 self.enumspecs = []
332                 self.enumprefix = None
333                 self.ignore_categs = []
334                 self.ignore_funcs = []
335
336                 path = os.path.split(fn)[0]
337
338                 for line in InputFile(fn):
339                         parts = line.split()
340                         if parts[0]=="typemap":
341                                 self.typemap = os.path.join(path, parts[1])
342                         elif parts[0]=="iomap":
343                                 self.iomap = os.path.join(path, parts[1])
344                         elif parts[0]=="spec":
345                                 self.specs.append(os.path.join(path, parts[1]))
346                         elif parts[0]=="errata":
347                                 self.errata.append(os.path.join(path, parts[1]))
348                         elif parts[0]=="prefix":
349                                 self.prefix = parts[1]
350                         elif parts[0]=="enumspec":
351                                 self.enumspecs.append(os.path.join(path, parts[1]))
352                         elif parts[0]=="enumprefix":
353                                 self.enumprefix = parts[1]
354                         elif parts[0]=="ignore":
355                                 if parts[1]=="category":
356                                         self.ignore_categs.append(parts[2])
357                                 elif parts[1]=="function":
358                                         self.ignore_funcs.append(parts[2])
359                         else:
360                                 sys.stderr.write("Unknown keyword '%s'\n", parts[0])
361
362
363 def read_spec(fn, prefix, funcs=None):
364         if funcs is None:
365                 funcs = []
366         cur_func = None
367
368         for line in InputFile(fn):
369                 if line.find(':')>=0:
370                         continue
371                 elif line[0]=='\t' and cur_func:
372                         parts = line.split()
373                         if parts[0]=="return":
374                                 cur_func.ret.set_type(parts[1], "out", "value")
375                         elif parts[0]=="param":
376                                 bracket = parts[4].find('[')
377                                 if bracket>=0:
378                                         parts.insert(5, parts[4][bracket:])
379                                         parts[4] = parts[4][:bracket]
380
381                                 param = cur_func.get_param(parts[1])
382                                 param.set_type(parts[2], parts[3], parts[4])
383                                 if len(parts)==6 or (len(parts)>6 and parts[6]!="retained"):
384                                         param.set_size(parts[5][1:-1])
385                         elif parts[0]=="category":
386                                 cur_func.set_category(parts[1])
387                         elif parts[0]=="glxvendorglx" and cur_func.category=="glx":
388                                 cur_func.set_category("glxext")
389                 else:
390                         paren = line.find('(')
391                         if paren>0:
392                                 cparen = line.rfind(')')
393                                 if cparen>paren+1:
394                                         pnames = [n.strip() for n in line[paren+1:cparen].split(",")]
395                                 else:
396                                         pnames = []
397                                 fname = prefix+line[:paren]
398                                 cur_func = None
399                                 for f in funcs:
400                                         if f.name==fname:
401                                                 cur_func = f
402                                                 break
403                                 if not cur_func:
404                                         cur_func = Function(prefix+line[:paren], pnames)
405                                         funcs.append(cur_func)
406
407         return funcs
408
409 def read_enums(fn, prefix):
410         enums = []
411         cur_categ = []
412
413         for line in InputFile(fn):
414                 if ':' in line:
415                         parts = line[:line.find(':')].split()
416                         if len(parts)==2 and parts[1]=="enum":
417                                 cur_categ = parts[0]
418                 elif cur_categ:
419                         parts = line.split()
420                         if parts[0]=="use":
421                                 enums.append(Enum(prefix+parts[2], cur_categ, None))
422                         elif parts[1]=="=":
423                                 try:
424                                         enums.append(Enum(prefix+parts[0], cur_categ, int(parts[2], 0)))
425                                 except ValueError, e:
426                                         sys.stderr.write("Syntax error in %s: %s\n"%(fn, e))
427
428         for e in enums:
429                 if e.value is None:
430                         for n in enums:
431                                 if n.name==e.name and n.value is not None:
432                                         e.value = n.value
433                         if e.value is None:
434                                 sys.stderr.write("Could not find value for enum reference %s in category %s\n"%(e.name, e.category))
435
436         return enums
437
438 parser = optparse.OptionParser()
439 parser.add_option("--depends", dest="depends", default=False)
440 (options, args) = parser.parse_args()
441
442 template = Template(args[0])
443 apis = []
444 for i in args[1:]:
445         apis.append(Api(i))
446
447 if options.depends:
448         deps = args[:]
449         if template.mode=="functions":
450                 for api in apis:
451                         deps += api.specs
452                         deps.append(api.typemap)
453                         deps.append(api.iomap)
454         elif template.mode=="enums":
455                 for api in apis:
456                         deps += api.enumspecs
457         sys.stdout.write("%s: %s\n"%(options.depends, " ".join(deps)))
458 else:
459         template.process(apis)