Procházet zdrojové kódy

mesa/es: Add APIspec.xml and its parser.

APIspec.xml is based on APIspec.txt.  The new format has less code
duplications and should be easier to read.

Signed-off-by: Chia-I Wu <olvaffe@gmail.com>
tags/7.8-rc1
Chia-I Wu před 15 roky
rodič
revize
0c1a7bbe0d

+ 52
- 0
src/mesa/es/main/APIspec.dtd Zobrazit soubor

@@ -0,0 +1,52 @@
<!ELEMENT apispec (template|api)+>

<!ELEMENT api (category*, function*)>
<!ELEMENT category EMPTY>
<!ELEMENT function EMPTY>

<!ELEMENT template (proto, desc*)>
<!ELEMENT proto (return, (param|vector)*)>
<!ELEMENT return EMPTY>
<!ELEMENT param EMPTY>
<!ELEMENT vector (param*)>
<!ELEMENT desc ((value|range)*, desc*)>
<!ELEMENT value EMPTY>
<!ELEMENT range EMPTY>

<!ATTLIST api name NMTOKEN #REQUIRED
implementation (true | false) "false">
<!ATTLIST category name NMTOKEN #REQUIRED>
<!ATTLIST function name NMTOKEN #REQUIRED
default_prefix NMTOKEN "_mesa_"
external (true | false) "false"
template NMTOKEN #REQUIRED
gltype CDATA #IMPLIED
vector_size NMTOKEN #IMPLIED
expand_vector (true | false) "false"
skip_desc (true | false) "false">

<!ATTLIST template name NMTOKEN #REQUIRED
direction (set | get) "set">

<!ATTLIST return type CDATA #REQUIRED>
<!ATTLIST param name NMTOKEN #REQUIRED
type CDATA #REQUIRED
hide_if_expanded (true | false) "false"
category NMTOKEN #IMPLIED>
<!ATTLIST vector name NMTOKEN #REQUIRED
type CDATA #REQUIRED
size NMTOKEN #REQUIRED
category NMTOKEN #IMPLIED>

<!ATTLIST desc name NMTOKEN #REQUIRED
vector_size CDATA #IMPLIED
convert (true | false) #IMPLIED
error NMTOKEN "GL_INVALID_ENUM"
category NMTOKEN #IMPLIED>

<!ATTLIST value name CDATA #REQUIRED
category NMTOKEN #IMPLIED>
<!ATTLIST range from NMTOKEN #REQUIRED
to NMTOKEN #REQUIRED
base NMTOKEN #IMPLIED
category NMTOKEN #IMPLIED>

+ 601
- 0
src/mesa/es/main/APIspec.py Zobrazit soubor

@@ -0,0 +1,601 @@
#!/usr/bin/python
#
# Copyright (C) 2009 Chia-I Wu <olv@0xlab.org>
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# on the rights to use, copy, modify, merge, publish, distribute, sub
# license, and/or sell copies of the Software, and to permit persons to whom
# the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice (including the next
# paragraph) shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
# IBM AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
"""
A parser for APIspec.
"""

class SpecError(Exception):
"""Error in the spec file."""


class Spec(object):
"""A Spec is an abstraction of the API spec."""

def __init__(self, doc):
self.doc = doc

self.spec_node = doc.getRootElement()
self.tmpl_nodes = {}
self.api_nodes = {}
self.impl_node = None

# parse <apispec>
node = self.spec_node.children
while node:
if node.type == "element":
if node.name == "template":
self.tmpl_nodes[node.prop("name")] = node
elif node.name == "api":
self.api_nodes[node.prop("name")] = node
else:
raise SpecError("unexpected node %s in apispec" %
node.name)
node = node.next

# find an implementation
for name, node in self.api_nodes.iteritems():
if node.prop("implementation") == "true":
self.impl_node = node
break
if not self.impl_node:
raise SpecError("unable to find an implementation")

def get_impl(self):
"""Return the implementation."""
return API(self, self.impl_node)

def get_api(self, name):
"""Return an API."""
return API(self, self.api_nodes[name])


class API(object):
"""An API consists of categories and functions."""

def __init__(self, spec, api_node):
self.name = api_node.prop("name")
self.is_impl = (api_node.prop("implementation") == "true")

self.categories = []
self.functions = []

# parse <api>
func_nodes = []
node = api_node.children
while node:
if node.type == "element":
if node.name == "category":
cat = node.prop("name")
self.categories.append(cat)
elif node.name == "function":
func_nodes.append(node)
else:
raise SpecError("unexpected node %s in api" % node.name)
node = node.next

# realize functions
for func_node in func_nodes:
tmpl_node = spec.tmpl_nodes[func_node.prop("template")]
try:
func = Function(tmpl_node, func_node, self.is_impl,
self.categories)
except SpecError, e:
func_name = func_node.prop("name")
raise SpecError("failed to parse %s: %s" % (func_name, e))
self.functions.append(func)

def match(self, func, conversions={}):
"""Find a matching function in the API."""
match = None
need_conv = False
for f in self.functions:
matched, conv = f.match(func, conversions)
if matched:
match = f
need_conv = conv
# exact match
if not need_conv:
break
return (match, need_conv)


class Function(object):
"""Parse and realize a <template> node."""

def __init__(self, tmpl_node, func_node, force_skip_desc=False, categories=[]):
self.tmpl_name = tmpl_node.prop("name")
self.direction = tmpl_node.prop("direction")

self.name = func_node.prop("name")
self.prefix = func_node.prop("default_prefix")
self.is_external = (func_node.prop("external") == "true")

if force_skip_desc:
self._skip_desc = True
else:
self._skip_desc = (self.is_external or func_node.prop("skip_desc") == "true")

self._categories = categories

# these attributes decide how the template is realized
self._gltype = func_node.prop("gltype")
if func_node.hasProp("vector_size"):
self._vector_size = int(func_node.prop("vector_size"))
else:
self._vector_size = 0
self._expand_vector = (func_node.prop("expand_vector") == "true")

self.return_type = "void"
param_nodes = []

# find <proto>
proto_node = tmpl_node.children
while proto_node:
if proto_node.type == "element" and proto_node.name == "proto":
break
proto_node = proto_node.next
if not proto_node:
raise SpecError("no proto")
# and parse it
node = proto_node.children
while node:
if node.type == "element":
if node.name == "return":
self.return_type = node.prop("type")
elif node.name == "param" or node.name == "vector":
if self.support_node(node):
# make sure the node is not hidden
if not (self._expand_vector and
(node.prop("hide_if_expanded") == "true")):
param_nodes.append(node)
else:
raise SpecError("unexpected node %s in proto" % node.name)
node = node.next

self._init_params(param_nodes)
self._init_descs(tmpl_node, param_nodes)

def __str__(self):
return "%s %s%s(%s)" % (self.return_type, self.prefix, self.name,
self.param_string(True))

def _init_params(self, param_nodes):
"""Parse and initialize parameters."""
self.params = []

for param_node in param_nodes:
size = self.param_node_size(param_node)
# when no expansion, vector is just like param
if param_node.name == "param" or not self._expand_vector:
param = Parameter(param_node, self._gltype, size)
self.params.append(param)
continue

if not size or size > param_node.lsCountNode():
raise SpecError("could not expand %s with unknown or "
"mismatch sizes" % param.name)

# expand the vector
expanded_params = []
child = param_node.children
while child:
if (child.type == "element" and child.name == "param" and
self.support_node(child)):
expanded_params.append(Parameter(child, self._gltype))
if len(expanded_params) == size:
break
child = child.next
# just in case that lsCountNode counts unknown nodes
if len(expanded_params) < size:
raise SpecError("not enough named parameters")

self.params.extend(expanded_params)

def _init_descs(self, tmpl_node, param_nodes):
"""Parse and initialize parameter descriptions."""
self.checker = Checker()
if self._skip_desc:
return

node = tmpl_node.children
while node:
if node.type == "element" and node.name == "desc":
if self.support_node(node):
# parse <desc>
desc = Description(node, self._categories)
self.checker.add_desc(desc)
node = node.next

self.checker.validate(self, param_nodes)

def support_node(self, node):
"""Return true if a node is in the supported category."""
return (not node.hasProp("category") or
node.prop("category") in self._categories)

def get_param(self, name):
"""Return the named parameter."""
for param in self.params:
if param.name == name:
return param
return None

def param_node_size(self, param):
"""Return the size of a vector."""
if param.name != "vector":
return 0

size = param.prop("size")
if size.isdigit():
size = int(size)
else:
size = 0
if not size:
size = self._vector_size
if not size and self._expand_vector:
# return the number of named parameters
size = param.lsCountNode()
return size

def param_string(self, declaration):
"""Return the C code of the parameters."""
args = []
if declaration:
for param in self.params:
sep = "" if param.type.endswith("*") else " "
args.append("%s%s%s" % (param.type, sep, param.name))
if not args:
args.append("void")
else:
for param in self.params:
args.append(param.name)
return ", ".join(args)

def match(self, other, conversions={}):
"""Return true if the functions match, probably with a conversion."""
if (self.tmpl_name != other.tmpl_name or
self.return_type != other.return_type or
len(self.params) != len(other.params)):
return (False, False)

need_conv = False
for i in xrange(len(self.params)):
src = other.params[i]
dst = self.params[i]
if (src.is_vector != dst.is_vector or src.size != dst.size):
return (False, False)
if src.type != dst.type:
if dst.base_type() in conversions.get(src.base_type(), []):
need_conv = True
else:
# unable to convert
return (False, False)

return (True, need_conv)


class Parameter(object):
"""A parameter of a function."""

def __init__(self, param_node, gltype=None, size=0):
self.is_vector = (param_node.name == "vector")

self.name = param_node.prop("name")
self.size = size

type = param_node.prop("type")
if gltype:
type = type.replace("GLtype", gltype)
elif type.find("GLtype") != -1:
raise SpecError("parameter %s has unresolved type" % self.name)

self.type = type

def base_type(self):
"""Return the base GL type by stripping qualifiers."""
return [t for t in self.type.split(" ") if t.startswith("GL")][0]


class Checker(object):
"""A checker is the collection of all descriptions on the same level.
Descriptions of the same parameter are concatenated.
"""

def __init__(self):
self.switches = {}

def add_desc(self, desc):
"""Add a description."""
# TODO take index into consideration
if desc.name not in self.switches:
self.switches[desc.name] = []
self.switches[desc.name].append(desc)

def validate(self, func, param_nodes):
"""Validate the checker against a function."""
tmp = Checker()

for switch in self.switches.itervalues():
valid_descs = []
for desc in switch:
if desc.validate(func, param_nodes):
valid_descs.append(desc)
# no possible values
if not valid_descs:
return False
for desc in valid_descs:
if not desc._is_noop:
tmp.add_desc(desc)

self.switches = tmp.switches
return True

def flatten(self, name=None):
"""Return a flat list of all descriptions of the named parameter."""
flat_list = []
for switch in self.switches.itervalues():
for desc in switch:
if not name or desc.name == name:
flat_list.append(desc)
flat_list.extend(desc.checker.flatten(name))
return flat_list

def always_check(self, name):
"""Return true if the parameter is checked in all possible pathes."""
if name in self.switches:
return True

# a param is always checked if any of the switch always checks it
for switch in self.switches.itervalues():
# a switch always checks it if all of the descs always check it
always = True
for desc in switch:
if not desc.checker.always_check(name):
always = False
break
if always:
return True
return False

def _c_switch(self, name, indent="\t"):
"""Output C switch-statement for the named parameter, for debug."""
switch = self.switches.get(name, [])
# make sure there are valid values
need_switch = False
for desc in switch:
if desc.values:
need_switch = True
if not need_switch:
return []

stmts = []
var = switch[0].name
if switch[0].index >= 0:
var += "[%d]" % switch[0].index
stmts.append("switch (%s) { /* assume GLenum */" % var)

for desc in switch:
if desc.values:
for val in desc.values:
stmts.append("case %s:" % val)
for dep_name in desc.checker.switches.iterkeys():
dep_stmts = [indent + s for s in desc.checker._c_switch(dep_name, indent)]
stmts.extend(dep_stmts)
stmts.append(indent + "break;")

stmts.append("default:")
stmts.append(indent + "ON_ERROR(%s);" % switch[0].error);
stmts.append(indent + "break;")
stmts.append("}")

return stmts

def dump(self, indent="\t"):
"""Dump the descriptions in C code."""
stmts = []
for name in self.switches.iterkeys():
c_switch = self._c_switch(name)
print "\n".join(c_switch)


class Description(object):
"""A description desribes a parameter and its relationship with other
parameters.
"""

def __init__(self, desc_node, categories=[]):
self._categories = categories
self._is_noop = False

self.name = desc_node.prop("name")
self.index = -1

self.error = desc_node.prop("error") or "GL_INVALID_ENUM"
# vector_size may be C code
self.size_str = desc_node.prop("vector_size")

self._has_enum = False
self.values = []
dep_nodes = []

# parse <desc>
valid_names = ["value", "range", "desc"]
node = desc_node.children
while node:
if node.type == "element":
if node.name in valid_names:
# ignore nodes that require unsupported categories
if (node.prop("category") and
node.prop("category") not in self._categories):
node = node.next
continue
else:
raise SpecError("unexpected node %s in desc" % node.name)

if node.name == "value":
val = node.prop("name")
if not self._has_enum and val.startswith("GL_"):
self._has_enum = True
self.values.append(val)
elif node.name == "range":
first = int(node.prop("from"))
last = int(node.prop("to"))
base = node.prop("base") or ""
if not self._has_enum and base.startswith("GL_"):
self._has_enum = True
# expand range
for i in xrange(first, last + 1):
self.values.append("%s%d" % (base, i))
else: # dependent desc
dep_nodes.append(node)
node = node.next

# default to convert if there is no enum
self.convert = not self._has_enum
if desc_node.hasProp("convert"):
self.convert = (desc_node.prop("convert") == "true")

self._init_deps(dep_nodes)

def _init_deps(self, dep_nodes):
"""Parse and initialize dependents."""
self.checker = Checker()

for dep_node in dep_nodes:
# recursion!
dep = Description(dep_node, self._categories)
self.checker.add_desc(dep)

def _search_param_node(self, param_nodes, name=None):
"""Search the template parameters for the named node."""
param_node = None
param_index = -1

if not name:
name = self.name
for node in param_nodes:
if name == node.prop("name"):
param_node = node
elif node.name == "vector":
child = node.children
idx = 0
while child:
if child.type == "element" and child.name == "param":
if name == child.prop("name"):
param_node = node
param_index = idx
break
idx += 1
child = child.next
if param_node:
break
return (param_node, param_index)

def _find_final(self, func, param_nodes):
"""Find the final parameter."""
param = func.get_param(self.name)
param_index = -1

# the described param is not in the final function
if not param:
# search the template parameters
node, index = self._search_param_node(param_nodes)
if not node:
raise SpecError("invalid desc %s in %s" %
(self.name, func.name))

# a named parameter of a vector
if index >= 0:
param = func.get_param(node.prop("name"))
param_index = index
elif node.name == "vector":
# must be an expanded vector, check its size
if self.size_str and self.size_str.isdigit():
size = int(self.size_str)
expanded_size = func.param_node_size(node)
if size != expanded_size:
return (False, None, -1)
# otherwise, it is a valid, but no-op, description

return (True, param, param_index)

def validate(self, func, param_nodes):
"""Validate a description against certain function."""
if self.checker.switches and not self.values:
raise SpecError("no valid values for %s" % self.name)

valid, param, param_index = self._find_final(func, param_nodes)
if not valid:
return False

# the description is valid, but the param is gone
# mark it no-op so that it will be skipped
if not param:
self._is_noop = True
return True

if param.is_vector:
# if param was known, this should have been done in __init__
if self._has_enum:
self.size_str = "1"
# size mismatch
if (param.size and self.size_str and self.size_str.isdigit() and
param.size != int(self.size_str)):
return False
elif self.size_str:
# only vector accepts vector_size
raise SpecError("vector_size is invalid for %s" % param.name)

if not self.checker.validate(func, param_nodes):
return False

# update the description
self.name = param.name
self.index = param_index

return True


def main():
import libxml2

filename = "APIspec.xml"
apinames = ["GLES1.1", "GLES2.0"]

doc = libxml2.readFile(filename, None,
libxml2.XML_PARSE_DTDLOAD +
libxml2.XML_PARSE_DTDVALID +
libxml2.XML_PARSE_NOBLANKS)

spec = Spec(doc)
impl = spec.get_impl()
for apiname in apinames:
spec.get_api(apiname)

doc.freeDoc()

print "%s is successfully parsed" % filename


if __name__ == "__main__":
main()

+ 4405
- 0
src/mesa/es/main/APIspec.xml
Diff nebyl zobrazen, protože je příliš veliký
Zobrazit soubor


+ 262
- 0
src/mesa/es/main/APIspecutil.py Zobrazit soubor

@@ -0,0 +1,262 @@
#!/usr/bin/python
#
# Copyright (C) 2009 Chia-I Wu <olv@0xlab.org>
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# on the rights to use, copy, modify, merge, publish, distribute, sub
# license, and/or sell copies of the Software, and to permit persons to whom
# the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice (including the next
# paragraph) shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
# IBM AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
"""
Minimal apiutil.py interface for use by es_generator.py.
"""

import sys
import libxml2

import APIspec

__spec = {}
__functions = {}
__aliases = {}

def _ParseXML(filename, apiname):
conversions = {
# from to
'GLfloat': [ 'GLdouble' ],
'GLclampf': [ 'GLclampd' ],
'GLubyte': [ 'GLfloat', 'GLdouble' ],
'GLint': [ 'GLfloat', 'GLdouble' ],
'GLfixed': [ 'GLfloat', 'GLdouble' ],
'GLclampx': [ 'GLclampf', 'GLclampd' ],
}

doc = libxml2.readFile(filename, None,
libxml2.XML_PARSE_DTDLOAD +
libxml2.XML_PARSE_DTDVALID +
libxml2.XML_PARSE_NOBLANKS)
spec = APIspec.Spec(doc)
impl = spec.get_impl()
api = spec.get_api(apiname)
doc.freeDoc()

__spec["impl"] = impl
__spec["api"] = api

for func in api.functions:
alias, need_conv = impl.match(func, conversions)
if not alias:
print >>sys.stderr, "Error: unable to dispatch %s" % func.name
alias = func

__functions[func.name] = func
__aliases[func.name] = (alias, need_conv)


def AllSpecials(notused=None):
"""Return a list of all external functions in the API."""
api = __spec["api"]

specials = []
for func in api.functions:
if func.is_external:
specials.append(func.name)

return specials


def GetAllFunctions(filename, api):
"""Return sorted list of all functions in the API."""
if not __spec:
_ParseXML(filename, api)

api = __spec["api"]
names = []
for func in api.functions:
names.append(func.name)
names.sort()
return names


def ReturnType(funcname):
"""Return the C return type of named function."""
func = __functions[funcname]
return func.return_type


def Properties(funcname):
"""Return list of properties of the named GL function."""
func = __functions[funcname]
return [func.direction]


def _ValidValues(func, param):
"""Return the valid values of a parameter."""
valid_values = []
switch = func.checker.switches.get(param.name, [])
for desc in switch:
# no dependent vector
if not desc.checker.switches:
for val in desc.values:
valid_values.append((val, None, None, [], desc.error, None))
continue

items = desc.checker.switches.items()
if len(items) > 1:
print >>sys.stderr, "%s: more than one parameter depend on %s" % \
(func.name, desc.name)
dep_name, dep_switch = items[0]

for dep_desc in dep_switch:
if dep_desc.index >= 0 and dep_desc.index != 0:
print >>sys.stderr, "%s: not first element of a vector" % func.name
if dep_desc.checker.switches:
print >>sys.stderr, "%s: deep nested dependence" % func.name

convert = None if dep_desc.convert else "noconvert"
for val in desc.values:
valid_values.append((val, dep_desc.size_str, dep_desc.name,
dep_desc.values, dep_desc.error, convert))
return valid_values


def _Conversion(func, src_param):
"""Return the destination type of the conversion, or None."""
alias, need_conv = __aliases[func.name]
if need_conv:
dst_param = alias.get_param(src_param.name)
if src_param.type == dst_param.type:
need_conv = False
if not need_conv:
return (None, "none")

converts = { True: 0, False: 0 }

# In Fogx, for example, pname may be GL_FOG_DENSITY/GL_FOG_START/GL_FOG_END
# or GL_FOG_MODE. In the former three cases, param is not checked and the
# default is to convert.
if not func.checker.always_check(src_param.name):
converts[True] += 1

for desc in func.checker.flatten(src_param.name):
converts[desc.convert] += 1
if converts[True] and converts[False]:
break

# it should be "never", "sometimes", and "always"...
if converts[False]:
if converts[True]:
conversion = "some"
else:
conversion = "none"
else:
conversion = "all"

return (dst_param.base_type(), conversion)


def _MaxVecSize(func, param):
"""Return the largest possible size of a vector."""
if not param.is_vector:
return 0
if param.size:
return param.size

# need to look at all descriptions
size = 0
for desc in func.checker.flatten(param.name):
if desc.size_str and desc.size_str.isdigit():
s = int(desc.size_str)
if s > size:
size = s
if not size:
need_conv = __aliases[func.name][1]
if need_conv:
print >>sys.stderr, \
"Error: unable to dicide the max size of %s in %s" % \
(param.name, func.name)
return size


def _ParameterTuple(func, param):
"""Return a parameter tuple.

[0] -- parameter name
[1] -- parameter type
[2] -- max vector size or 0
[3] -- dest type the parameter converts to, or None
[4] -- valid values
[5] -- how often does the conversion happen

"""
vec_size = _MaxVecSize(func, param)
dst_type, conversion = _Conversion(func, param)
valid_values = _ValidValues(func, param)

return (param.name, param.type, vec_size, dst_type, valid_values, conversion)


def Parameters(funcname):
"""Return list of tuples of function parameters."""
func = __functions[funcname]
params = []
for param in func.params:
params.append(_ParameterTuple(func, param))

return params


def FindParamIndex(params, paramname):
"""Find the index of a named parameter."""
for i in xrange(len(params)):
if params[i][0] == paramname:
return i
return None


def MakeDeclarationString(params):
"""Return a C-style parameter declaration string."""
string = []
for p in params:
sep = "" if p[1].endswith("*") else " "
string.append("%s%s%s" % (p[1], sep, p[0]))
if not string:
return "void"
return ", ".join(string)


def AliasPrefix(funcname):
"""Return the prefix of the function the named function is an alias of."""
alias = __aliases[funcname][0]
return alias.prefix


def Alias(funcname):
"""Return the name of the function the named function is an alias of."""
alias, need_conv = __aliases[funcname]
return alias.name if not need_conv else None


def ConversionFunction(funcname):
"""Return the name of the function the named function converts to."""
alias, need_conv = __aliases[funcname]
return alias.name if need_conv else None


def Categories(funcname):
"""Return all the categories of the named GL function."""
api = __spec["api"]
return [api.name]

Načítá se…
Zrušit
Uložit