parse.py (10381B)
1 try: 2 from lxml import etree 3 from lxml.etree import ETCompatXMLParser as parser 4 5 def xml_fromstring(argument): 6 return etree.fromstring(argument, parser=parser()) 7 8 def xml_frompath(path): 9 return etree.parse(path, parser=parser()).getroot() 10 except ImportError: 11 try: 12 import xml.etree.cElementTree as etree 13 except ImportError: 14 import xml.etree.ElementTree as etree 15 16 def xml_fromstring(argument): 17 return etree.fromstring(argument) 18 19 def xml_frompath(path): 20 return etree.parse(path).getroot() 21 22 23 from collections import defaultdict, OrderedDict 24 from contextlib import closing 25 from itertools import chain 26 import re 27 28 from glad.opener import URLOpener 29 30 31 _ARRAY_RE = re.compile(r'\[\d*\]') 32 33 34 class Spec(object): 35 API = 'https://cvs.khronos.org/svn/repos/ogl/trunk/doc/registry/public/api/' 36 NAME = '' 37 38 def __init__(self, root): 39 self.root = root 40 41 self._types = None 42 self._groups = None 43 self._enums = None 44 self._commands = None 45 self._features = None 46 self._extensions = None 47 48 @classmethod 49 def from_url(cls, url, opener=None): 50 if opener is None: 51 opener = URLOpener.default() 52 53 with closing(opener.urlopen(url)) as f: 54 raw = f.read() 55 56 return cls(xml_fromstring(raw)) 57 58 @classmethod 59 def from_svn(cls, opener=None): 60 return cls.from_url(cls.API + cls.NAME + '.xml', opener=opener) 61 62 @classmethod 63 def fromstring(cls, string): 64 return cls(xml_fromstring(string)) 65 66 @classmethod 67 def from_file(cls, path): 68 return cls(xml_frompath(path)) 69 70 @property 71 def comment(self): 72 return self.root.find('comment').text 73 74 @property 75 def types(self): 76 if self._types is None: 77 self._types = [Type(element) for element in 78 self.root.find('types').iter('type')] 79 return self._types 80 81 @property 82 def groups(self): 83 if self._groups is None: 84 self._groups = dict([(element.attrib['name'], Group(element)) 85 for element in self.root.find('groups')]) 86 return self._groups 87 88 @property 89 def commands(self): 90 if self._commands is None: 91 self._commands = dict([(element.find('proto').find('name').text, 92 Command(element, self)) 93 for element in self.root.find('commands')]) 94 return self._commands 95 96 @property 97 def enums(self): 98 if self._enums is not None: 99 return self._enums 100 101 self._enums = dict() 102 for element in self.root.iter('enums'): 103 namespace = element.attrib['namespace'] 104 type_ = element.get('type') 105 group = element.get('group') 106 vendor = element.get('vendor') 107 comment = element.get('comment', '') 108 109 for enum in element: 110 if enum.tag == 'unused': 111 continue 112 assert enum.tag == 'enum' 113 114 name = enum.attrib['name'] 115 self._enums[name] = Enum(name, enum.attrib['value'], namespace, 116 type_, group, vendor, comment) 117 118 return self._enums 119 120 @property 121 def features(self): 122 if self._features is not None: 123 return self._features 124 125 self._features = defaultdict(OrderedDict) 126 for element in self.root.iter('feature'): 127 num = tuple(map(int, element.attrib['number'].split('.'))) 128 self._features[element.attrib['api']][num] = Feature(element, self) 129 130 return self._features 131 132 @property 133 def extensions(self): 134 if self._extensions is not None: 135 return self._extensions 136 137 self._extensions = defaultdict(dict) 138 for element in self.root.find('extensions'): 139 for api in element.attrib['supported'].split('|'): 140 self._extensions[api][element.attrib['name']] = Extension(element, self) 141 142 return self._extensions 143 144 145 class Type(object): 146 def __init__(self, element): 147 apientry = element.find('apientry') 148 if apientry is not None: 149 apientry.text = 'APIENTRY' 150 self.raw = ''.join(element.itertext()) 151 self.api = element.get('api') 152 self.name = element.get('name') 153 154 @property 155 def is_preprocessor(self): 156 return '#' in self.raw 157 158 159 class Group(object): 160 def __init__(self, element): 161 self.name = element.attrib['name'] 162 self.enums = [enum.attrib['name'] for enum in element] 163 164 165 class Enum(object): 166 def __init__(self, name, value, namespace, type_=None, 167 group=None, vendor=None, comment=''): 168 self.name = name 169 self.value = value 170 self.namespace = namespace 171 self.type = type_ 172 self.group = group 173 self.vendor = vendor 174 self.comment = comment 175 176 def __hash__(self): 177 return hash(self.name) 178 179 def __str__(self): 180 return self.name 181 182 __repr__ = __str__ 183 184 185 class Command(object): 186 def __init__(self, element, spec): 187 self.proto = Proto(element.find('proto')) 188 self.params = [Param(ele, spec) for ele in element.iter('param')] 189 190 def __hash__(self): 191 return hash(self.proto.name) 192 193 def __str__(self): 194 return '{self.proto.name}'.format(self=self) 195 196 __repr__ = __str__ 197 198 199 class Proto(object): 200 def __init__(self, element): 201 self.name = element.find('name').text 202 self.ret = OGLType(element) 203 204 def __str__(self): 205 return '{self.ret} {self.name}'.format(self=self) 206 207 208 class Param(object): 209 def __init__(self, element, spec): 210 self.group = element.get('group') 211 self.type = OGLType(element) 212 self.name = element.find('name').text.strip('*') 213 214 def __str__(self): 215 return '{0!r} {1}'.format(self.type, self.name) 216 217 218 class OGLType(object): 219 def __init__(self, element): 220 self.element = element 221 self.raw = ''.join(element.itertext()).strip() 222 223 self.name = element.find('name').text 224 225 self.type = (self.raw.replace('const', '').replace('unsigned', '') 226 .replace('struct', '').strip().split(None, 1)[0] 227 if element.find('ptype') is None else element.find('ptype').text) 228 # 0 if no pointer, 1 if *, 2 if ** 229 self.is_pointer = 0 if self.raw is None else self.raw.count('*') 230 # it can be a pointer to an array, or just an array 231 self.is_pointer += len(_ARRAY_RE.findall(self.raw)) 232 self.is_const = False if self.raw is None else 'const' in self.raw 233 self.is_unsigned = False if self.raw is None else 'unsigned' in self.raw 234 235 if 'struct' in self.raw and 'struct' not in self.type: 236 self.type = 'struct {}'.format(self.type) 237 238 ptype = element.find('ptype') 239 self.ptype = ptype.text if ptype is not None else None 240 241 def to_d(self): 242 if self.is_pointer > 1 and self.is_const: 243 s = 'const({}{}*)'.format('u' if self.is_unsigned else '', self.type) 244 s += '*' * (self.is_pointer - 1) 245 else: 246 t = '{}{}'.format('u' if self.is_unsigned else '', self.type) 247 s = 'const({})'.format(t) if self.is_const else t 248 s += '*' * self.is_pointer 249 return s.replace('struct ', '') 250 251 to_volt = to_d 252 253 def to_c(self): 254 result = '' 255 for text in self.element.itertext(): 256 if text == self.name: 257 # yup * is sometimes part of the name 258 result += '*' * text.count('*') 259 else: 260 result += text 261 result = _ARRAY_RE.sub('*', result) 262 return result.strip() 263 264 NIM_POINTER_MAP = { 265 'void': 'pointer', 266 'GLchar': 'cstring', 267 'struct _cl_context': 'ClContext', 268 'struct _cl_event': 'ClEvent' 269 } 270 271 def to_nim(self): 272 if self.is_pointer == 2: 273 s = 'cstringArray' if self.type == 'GLchar' else 'ptr pointer' 274 else: 275 s = self.type 276 if self.is_pointer == 1: 277 default = 'ptr ' + s 278 s = self.NIM_POINTER_MAP.get(s, default) 279 return s 280 281 __str__ = to_d 282 __repr__ = __str__ 283 284 285 class Extension(object): 286 def __init__(self, element, spec): 287 self.name = element.attrib['name'] 288 289 self.require = [] 290 for required in chain.from_iterable(element.findall('require')): 291 if required.tag == 'type': 292 continue 293 294 data = {'enum': spec.enums, 'command': spec.commands}[required.tag] 295 try: 296 self.require.append(data[required.attrib['name']]) 297 except KeyError: 298 pass # TODO 299 300 @property 301 def enums(self): 302 for r in self.require: 303 if isinstance(r, Enum): 304 yield r 305 306 @property 307 def functions(self): 308 for r in self.require: 309 if isinstance(r, Command): 310 yield r 311 312 def __hash__(self): 313 return hash(self.name) 314 315 def __str__(self): 316 return self.name 317 318 __repr__ = __str__ 319 320 321 class Feature(Extension): 322 def __init__(self, element, spec): 323 Extension.__init__(self, element, spec) 324 self.spec = spec 325 326 # not every spec has a ._remove member, but there shouldn't be a remove 327 # tag without that member, if there is, blame me! 328 for removed in chain.from_iterable(element.findall('remove')): 329 if removed.tag == 'type': 330 continue 331 332 data = {'enum': spec.enums, 'command': spec.commands}[removed.tag] 333 try: 334 spec._remove.add(data[removed.attrib['name']]) 335 except KeyError: 336 pass # TODO 337 338 self.number = tuple(map(int, element.attrib['number'].split('.'))) 339 self.api = element.attrib['api'] 340 341 def __str__(self): 342 return '{self.name}@{self.number!r}'.format(self=self) 343 344 @property 345 def enums(self): 346 for enum in super(Feature, self).enums: 347 if enum not in getattr(self.spec, 'removed', []): 348 yield enum 349 350 @property 351 def functions(self): 352 for func in super(Feature, self).functions: 353 if func not in getattr(self.spec, 'removed', []): 354 yield func 355 356 __repr__ = __str__