diff options
author | Pearu Peterson <pearu.peterson@gmail.com> | 2007-08-05 15:23:30 +0000 |
---|---|---|
committer | Pearu Peterson <pearu.peterson@gmail.com> | 2007-08-05 15:23:30 +0000 |
commit | 4196b728240d9225c38a6d630b5c117194c7577e (patch) | |
tree | fe11d67f56d7c6d62de2fbfb0bedce8e9b4e8e50 /numpy/f2py | |
parent | 2ff91f96ae8ed6c53c5822696e5527f12b9e1e2c (diff) | |
download | numpy-4196b728240d9225c38a6d630b5c117194c7577e.tar.gz |
Impl CType classes and rewrote docs.
Diffstat (limited to 'numpy/f2py')
-rw-r--r-- | numpy/f2py/lib/extgen/__init__.py | 4 | ||||
-rw-r--r-- | numpy/f2py/lib/extgen/base.py | 204 | ||||
-rw-r--r-- | numpy/f2py/lib/extgen/c_code.py | 6 | ||||
-rw-r--r-- | numpy/f2py/lib/extgen/c_type.py | 283 | ||||
-rw-r--r-- | numpy/f2py/lib/extgen/doc.txt | 263 | ||||
-rw-r--r-- | numpy/f2py/lib/extgen/extension_module.py | 35 | ||||
-rw-r--r-- | numpy/f2py/lib/extgen/predefined_components.py | 4 | ||||
-rw-r--r-- | numpy/f2py/lib/extgen/pyc_argument.py | 24 | ||||
-rw-r--r-- | numpy/f2py/lib/extgen/pyc_function.py | 31 |
9 files changed, 541 insertions, 313 deletions
diff --git a/numpy/f2py/lib/extgen/__init__.py b/numpy/f2py/lib/extgen/__init__.py index 7e89614cb..740793c9a 100644 --- a/numpy/f2py/lib/extgen/__init__.py +++ b/numpy/f2py/lib/extgen/__init__.py @@ -11,4 +11,8 @@ from pyc_function import PyCFunction from pyc_argument import PyCArgument from c_code import CCode +import c_type +from c_type import * +__all__ += c_type.__all__ + import predefined_components diff --git a/numpy/f2py/lib/extgen/base.py b/numpy/f2py/lib/extgen/base.py index 6a340100c..6efe042d0 100644 --- a/numpy/f2py/lib/extgen/base.py +++ b/numpy/f2py/lib/extgen/base.py @@ -1,58 +1,70 @@ """ ExtGen --- Python Extension module Generator. -Defines Base and Container classes. +Defines Component and Container classes. """ import re import sys import time -class BaseMetaClass(type): +class ComponentMetaClass(type): classnamespace = {} def __init__(cls,*args,**kws): n = cls.__name__ - c = BaseMetaClass.classnamespace.get(n) + c = ComponentMetaClass.classnamespace.get(n) if c is None: - BaseMetaClass.classnamespace[n] = cls + ComponentMetaClass.classnamespace[n] = cls else: print 'Ignoring redefinition of %s: %s defined earlier than %s' % (n, c, cls) type.__init__(cls, *args, **kws) def __getattr__(cls, name): - try: return BaseMetaClass.classnamespace[name] + try: return ComponentMetaClass.classnamespace[name] except KeyError: pass raise AttributeError("'%s' object has no attribute '%s'"% (cls.__name__, name)) -class Base(object): +class Component(object): # XXX: rename Component to Component - __metaclass__ = BaseMetaClass + __metaclass__ = ComponentMetaClass container_options = dict() component_container_map = dict() + default_container_label = None + default_component_class_name = 'CCode' template = '' def __new__(cls, *args, **kws): obj = object.__new__(cls) - obj._args = args obj._provides = kws.get('provides', None) obj.parent = None obj.containers = {} # holds containers for named string lists - obj.components = [] # holds pairs (<Base subclass instance>, <container name or None>) + obj.components = [] # holds pairs (<Component subclass instance>, <container name or None>) obj.initialize(*args, **kws) # initialize from constructor arguments return obj - def initialize(self, *args, **kws): + def initialize(self, *components, **options): """ Set additional attributes, add components to instance, etc. """ # self.myattr = .. - # map(self.add, args) + # map(self.add, components) return + @property + def provides(self): + """ + Return a code idiom name that the current class defines. + + Used in avoiding redefinitions of functions and variables. + """ + if self._provides is None: + return '%s_%s' % (self.__class__.__name__, id(self)) + return self._provides + @staticmethod def warning(message): print >> sys.stderr, 'extgen:',message @@ -61,59 +73,48 @@ class Base(object): print >> sys.stderr, message def __repr__(self): - return '%s%s' % (self.__class__.__name__, `self._args`) + return '%s%s' % (self.__class__.__name__, `self.containers`) def __getattr__(self, attr): - if attr.startswith('container_'): + if attr.startswith('container_'): # convenience feature return self.get_container(attr[10:]) raise AttributeError('%s instance has no attribute %r' % (self.__class__.__name__, attr)) - def get_container(self, key): - """ Return named container. - - Rules for returning containers: - (1) return local container if exists - (2) return parent container if exists - (3) create local container and return it with warning - """ - # local container - try: - return self.containers[key] - except KeyError: - pass - - # parent container - parent = self.parent - while parent is not None: - try: - return parent.containers[key] - except KeyError: - parent = parent.parent - continue - - # create local container - self.warning('Created container for %r with name %r, define it in'\ - ' .container_options mapping to get rid of this warning' \ - % (self.__class__.__name__, key)) - c = self.containers[key] = Container() - return c + def __add__(self, other): # convenience method + self.add(other) + return self + __iadd__ = __add__ - @property - def provides(self): - """ - Return a code idiom name that the current class defines. + @staticmethod + def _get_class_names(cls): + if not issubclass(cls, Component): + return [cls] + r = [cls] + for b in cls.__bases__: + r += Component._get_class_names(b) + return r - Used in avoiding redefinitions of functions and variables. - """ - if self._provides is None: - return '%s_%s' % (self.__class__.__name__, id(self)) - return self._provides - - def get_templates(self): + def add(self, component, container_label=None): """ - Return instance templates. + Append component and its target container label to components list. """ - return self.template + if not isinstance(component, Component) and self.default_component_class_name!=component.__class__.__name__: + clsname = self.default_component_class_name + if clsname is not None: + component = getattr(Component, clsname)(component) + else: + raise ValueError('%s.add requires Component instance but got %r' \ + % (self.__class__.__name__, component.__class__.__name__)) + if container_label is None: + container_label = self.default_container_label + for n in self._get_class_names(component.__class__): + try: + container_label = self.component_container_map[n.__name__] + break + except KeyError: + pass + self.components.append((component, container_label)) + return def generate(self): """ @@ -135,10 +136,22 @@ class Base(object): # generate component code idioms for component, container_key in self.components: + if not isinstance(component, Component): + result = str(component) + if container_key == '<IGNORE>': + pass + elif container_key is not None: + self.get_container(container_key).add(result) + else: + self.warning('%s: no container label specified for component %r'\ + % (self.__class__.__name__,component)) + continue old_parent = component.parent component.parent = self result = component.generate() - if container_key is not None: + if container_key == '<IGNORE>': + pass + elif container_key is not None: if isinstance(container_key, tuple): assert len(result)==len(container_key),`len(result),container_key` results = result @@ -151,8 +164,8 @@ class Base(object): container = component.get_container(k) container.add(r, component.provides) else: - self.warning('no label specified for component %r, ignoring its result'\ - % (component.provides)) + self.warning('%s: no container label specified for component providing %r'\ + % (self.__class__.__name__,component.provides)) component.parent = old_parent # update code idioms @@ -184,32 +197,41 @@ class Base(object): # container.add(<string>, label=None) return - def __iadd__(self, other): - """ Convenience add. + def get_container(self, name): + """ Return named container. + + Rules for returning containers: + (1) return local container if exists + (2) return parent container if exists + (3) create local container and return it with warning """ - self.add(other) - return self + # local container + try: + return self.containers[name] + except KeyError: + pass + + # parent container + parent = self.parent + while parent is not None: + try: + return parent.containers[name] + except KeyError: + parent = parent.parent + continue - def add(self, component, container_label=None): + # create local container + self.warning('Created container for %r with name %r, define it in'\ + ' .container_options mapping to get rid of this warning' \ + % (self.__class__.__name__, name)) + c = self.containers[name] = Container() + return c + + def get_templates(self): """ - Append component and its target container label to components list. + Return instance templates. """ - if isinstance(component, str): - component = Base.CCode(component) - if container_label is None: - container_label = self.component_container_map.get(component.__class__.__name__, None) - assert isinstance(component, Base), `type(component)` - self.components.append((component, container_label)) - - @property - def show(self): - # display the content of containers - self.generate() - r = [self.__class__.__name__] - for k, v in self.containers.items(): - if v.list: - r.append('--- %s ---\n%s' % (k,v)) - return '\n'.join(r) + return self.template def evaluate(self, template, **attrs): """ @@ -235,7 +257,8 @@ class Base(object): template = template[:i] + str(container) + template[i+len(s):] container.indent_offset = old_indent template = template % d - return re.sub(r'.*[<]KILLLINE[>].*\n','', template) + return re.sub(r'.*[<]KILLLINE[>].*(\n|$)','', template) + _registered_components_map = {} @@ -245,11 +268,11 @@ class Base(object): Register components so that component classes can use predefined components via `.get(<provides>)` method. """ - d = Base._registered_components_map + d = Component._registered_components_map for component in components: provides = component.provides if d.has_key(provides): - Base.warning('component that provides %r is already registered, ignoring.' % (provides)) + Component.warning('component that provides %r is already registered, ignoring.' % (provides)) else: d[provides] = component return @@ -260,7 +283,7 @@ class Base(object): Return predefined component with given provides property.. """ try: - return Base._registered_components_map[provides] + return Component._registered_components_map[provides] except KeyError: pass raise KeyError('no registered component provides %r' % (provides)) @@ -269,6 +292,7 @@ class Base(object): def numpy_version(self): import numpy return numpy.__version__ + class Container(object): """ @@ -292,7 +316,7 @@ class Container(object): "hey, hoo, bar" """ - __metaclass__ = BaseMetaClass + __metaclass__ = ComponentMetaClass def __init__(self, separator='\n', prefix='', suffix='', @@ -303,6 +327,7 @@ class Container(object): use_indent = False, indent_offset = 0, use_firstline_indent = False, # implies use_indent + replace_map = {} ): self.list = [] self.label_map = {} @@ -318,8 +343,9 @@ class Container(object): self.use_indent = use_indent or use_firstline_indent self.indent_offset = indent_offset self.use_firstline_indent = use_firstline_indent + self.replace_map = replace_map - def __notzero__(self): + def __nonzero__(self): return bool(self.list) def has(self, label): @@ -353,6 +379,8 @@ class Container(object): if d!=content: raise ValueError("Container item %r exists with different value" % (label)) return + for old, new in self.replace_map.items(): + content = content.replace(old, new) self.list.append(content) self.label_map[label] = len(self.list)-1 return @@ -396,7 +424,9 @@ class Container(object): default = self.default, reverse=self.reverse, user_defined_str = self.user_str, use_indent = self.use_indent, - indent_offset = self.indent_offset + indent_offset = self.indent_offset, + use_firstline_indent = self.use_firstline_indent, + replace_map = self.replace_map ) options.update(extra_options) cpy = Container(**options) diff --git a/numpy/f2py/lib/extgen/c_code.py b/numpy/f2py/lib/extgen/c_code.py index eed661f7b..898a048f9 100644 --- a/numpy/f2py/lib/extgen/c_code.py +++ b/numpy/f2py/lib/extgen/c_code.py @@ -1,7 +1,7 @@ -from base import Base +from base import Component -class CCode(Base): +class CCode(Component): """ CCode(*lines, provides=..) @@ -27,6 +27,6 @@ class CCode(Base): assert label is None,`label` self.lines.extend(component.lines) else: - Base.add(self, component. label) + Component.add(self, component. label) diff --git a/numpy/f2py/lib/extgen/c_type.py b/numpy/f2py/lib/extgen/c_type.py index a8c21e48f..157d227a7 100644 --- a/numpy/f2py/lib/extgen/c_type.py +++ b/numpy/f2py/lib/extgen/c_type.py @@ -1,136 +1,199 @@ """ -Defines C type declaration templates: +C types. - CTypeAlias(name, ctype) --- typedef ctype name; - CTypeFunction(name, rtype, atype1, atype2,..) --- typedef rtype (*name)(atype1, atype2,...); - CTypeStruct(name, (name1,type1), (name2,type2), ...) --- typedef struct { type1 name1; type2 name2; .. } name; - CTypePtr(ctype) --- ctype * - CInt(), CLong(), ... --- int, long, ... - CPyObject() -The instances of CTypeBase have the following public methods and properties: - - - .asPtr() - - .declare(name) """ +__all__ = ['CType', 'CTypeAlias', 'CTypeFuncAlias', 'CTypePtr', 'CTypeStruct', 'CDecl'] -from base import Base - -class CTypeBase(Base): - - def declare(self, name): - return '%s %s;' % (self.typename, name) - - def __str__(self): - return self.typename - - def asPtr(self): - return CTypePtr(self) - -class CTypeAlias(CTypeBase): - - def __new__(cls, typename, ctype): - obj = Base.__new__(cls) - assert isinstance(ctype, CTypeBase),`type(ctype)` - obj.add(typename, ctype) - return obj - - @property - def typename(self): return self.components[0][0] - @property - def ctype(self): return self.components[0][1] +from base import Component - def local_generate(self, params=None): - container = self.get_container('TypeDef') - container.add(self.typename, 'typedef %s %s;' % (self.ctype, self.typename)) - return self.declare(params) +class CTypeBase(Component): + template = '%(name)s' + template_typedef = '' + default_container_label = '<IGNORE>' + default_component_class_name = 'CType' -class CTypeFunction(CTypeBase): - - def __new__(cls, typename, rctype, *arguments): - obj = Base.__new__(cls) - assert isinstance(rctype, CTypeBase),`type(rctype)` - obj.add(typename, rctype) - for i in range(len(arguments)): - a = arguments[i] - assert isinstance(a, CTypeBase),`type(a)` - obj.add('_a%i' % (i), a) - return obj - - @property - def typename(self): return self.components[0][0] - @property - def rctype(self): return self.components[0][1] @property - def arguments(self): return [v for n,v in self.components[1:]] + def provides(self): + return '%s_%s' % (self.__class__.__name__, self.name) - def local_generate(self, params=None): - container = self.get_container('TypeDef') - container.add(self.typename, 'typedef %s (*%s)(%s);' \ - % (self.rctype, self.typename, - ', '.join([str(ctype) for ctype in self.arguments]))) - return self.declare(params) + def initialize(self, name, *components): + self.name = name + map(self.add, components) -class CTypeStruct(CTypeBase): + def update_containers(self): + self.container_TypeDef += self.evaluate(self.template_typedef) - def __new__(cls, typename, *components): - obj = Base.__new__(cls, typename) - for n,v in components: - assert isinstance(v,CTypeBase),`type(v)` - obj.add(n,v) - return obj + def __str__(self): + return self.name - @property - def typename(self): return self._args[0] +class _CatchTypeDef(Component): # for doctest + template = '%(TypeDef)s' + default_container_label = '<IGNORE>' + container_options = dict(TypeDef=dict(default='')) + def initialize(self, ctype): + self.add(ctype) - def local_generate(self, params=None): - container = self.get_container('TypeDef') - decls = [ctype.declare(name) for name, ctype in self.components] - if decls: - d = 'typedef struct {\n %s\n} %s;' % ('\n '.join(decls),self.typename) - else: - d = 'typedef struct {} %s;' % (self.typename) - container.add(self.typename, d) - return self.declare(params) +class CType(CTypeBase): -class CTypePtr(CTypeBase): + """ CType(<name>) - def __new__(cls, ctype): - obj = Base.__new__(cls) - assert isinstance(ctype, CTypeBase),`type(ctype)` - obj.add('*', ctype) - return obj + Represents any predefined type in C. - @property - def ctype(self): return self.components[0][1] - - @property - def typename(self): - return self.ctype.typename + '*' + >>> cint = CType('int') + >>> print cint + int + >>> _CatchTypeDef(cint).generate() + '' + """ - def local_generate(self, params=None): - return self.declare(params) + def initialize(self, name): + self.name = name -class CTypeDefined(CTypeBase): + def update_containers(self): + pass - @property - def typename(self): return self._args[0] +class CTypeAlias(CTypeBase): -class CTypeIntrinsic(CTypeDefined): + """ CTypeAlias(<name>, <ctype>) + + >>> aint = CTypeAlias('aint', 'int') + >>> print aint + aint + >>> print _CatchTypeDef(aint).generate() + typedef int aint; + """ + + template_typedef = 'typedef %(ctype_name)s %(name)s;' + + def initialize(self, name, ctype): + self.name = name + if isinstance(ctype, str): ctype = CType(ctype) + self.ctype_name = ctype.name + self.add(ctype) + +class CTypeFuncAlias(CTypeBase): + + """ + CTypeFuncAlias(<name>, <return ctype>, *(<argument ctypes>)) + + >>> ifunc = CTypeFuncAlias('ifunc', 'int') + >>> print ifunc + ifunc + >>> print _CatchTypeDef(ifunc).generate() + typedef int (*ifunc)(void); + >>> ifunc += 'double' + >>> print _CatchTypeDef(ifunc).generate() + typedef int (*ifunc)(double); + """ + + template_typedef = 'typedef %(RCType)s (*%(name)s)(%(ACType)s);' + container_options = dict(RCType = dict(default='void'), + ACType = dict(default='void', separator=', ')) + component_container_map = dict(CType = 'ACType') + default_component_class_name = 'CType' + + def initialize(self, name, *components): + self.name = name + if components: + self.add(components[0], 'RCType') + map(self.add, components[1:]) - def __new__(cls, typename): - return Base.__new__(cls, typename) +class CTypePtr(CTypeBase): -class CPyObject(CTypeDefined): - def __new__(cls): - return Base.__new__(cls, 'PyObject') + """ + CTypePtr(<ctype>) + + >>> int_ptr = CTypePtr('int') + >>> print int_ptr + int_ptr + >>> print _CatchTypeDef(int_ptr).generate() + typedef int* int_ptr; + >>> int_ptr_ptr = CTypePtr(int_ptr) + >>> print int_ptr_ptr + int_ptr_ptr + >>> print _CatchTypeDef(int_ptr_ptr).generate() + typedef int* int_ptr; + typedef int_ptr* int_ptr_ptr; + """ + + template_typedef = 'typedef %(ctype_name)s* %(name)s;' + + def initialize(self, ctype): + if isinstance(ctype, str): ctype = CType(ctype) + self.name = '%s_ptr' % (ctype) + self.ctype_name = ctype.name + self.add(ctype) -class CInt(CTypeIntrinsic): +class CTypeStruct(CTypeBase): - def __new__(cls): - return Base.__new__(cls, 'int') + """ + CTypeStruct(<name>, *(<declarations>)) + + >>> s = CTypeStruct('s', CDecl('int','a')) + >>> print s + s + >>> print _CatchTypeDef(s).generate() + typedef struct { + int a; + } s; + >>> s += CDecl(CTypeFuncAlias('ft'), 'f') + >>> print _CatchTypeDef(s).generate() + typedef void (*ft)(void); + typedef struct { + int a; + ft f; + } s; + + """ + + container_options = dict(Decl = dict(default='<KILLLINE>', use_indent=True)) + default_component_class_name = None #'CDecl' + component_container_map = dict(CDecl='Decl') + + template_typedef = '''\ +typedef struct { + %(Decl)s +} %(name)s;''' + + def initialize(self, name, *components): + self.name = name + map(self.add, components) + +class CDecl(Component): + + """ + CDecl(<ctype>, *(<names with or without initialization>)) + + >>> ad = CDecl('int') + >>> ad.generate() + '' + >>> ad += 'a' + >>> print ad.generate() + int a; + >>> ad += 'b' + >>> print ad.generate() + int a, b; + >>> ad += 'c = 1' + >>> print ad.generate() + int a, b, c = 1; + """ + + template = '%(CTypeName)s %(Names)s;' + container_options = dict(Names=dict(default='<KILLLINE>', separator=', '), + CTypeName=dict()) + default_component_class_name = 'str' + component_container_map = dict(str = 'Names') + + def initialize(self, ctype, *names): + if isinstance(ctype, str): ctype = CType(ctype) + self.add(ctype, 'CTypeName') + map(self.add, names) + + +if 0: def local_generate(self, params=None): container = self.get_container('CAPICode') @@ -154,3 +217,9 @@ static PyObject* pyobj_from_int(int* value) { return self.declare(params) +def _test(): + import doctest + doctest.testmod() + +if __name__ == "__main__": + _test() diff --git a/numpy/f2py/lib/extgen/doc.txt b/numpy/f2py/lib/extgen/doc.txt index 9f76f6f1a..c971f22c0 100644 --- a/numpy/f2py/lib/extgen/doc.txt +++ b/numpy/f2py/lib/extgen/doc.txt @@ -22,77 +22,139 @@ Hello example follows:: >>> f = PyCFunction('hello') >>> f += 'printf("Hello!\\n");' >>> m += f - >>> m.generate() # returns a string containing C source to extension module + >>> print m.generate() # shows a string containing C source to extension module >>> foo = m.build() >>> foo.hello() Hello! >>> -Extending ExtGen -================ +Description of the ExtGen model +=============================== To extend ExtGen, one needs to understand the infrastructure of generating extension modules. -The `extgen` package provides many classes that are derived from Base -class (defined in extgen/base.py). Each such a class represents -certain code block or a code idiom in an extension module that is -defined in `.template` attribute. Most important `Base` methods, that -are used to generate code idioms, are: `.initialize()`, `.add()`, -`.generate()`, `init_containers()`, `.update_containers()`, -`.get_templates()`. - -Creating an extension module is carried out by the following steps: - -- create and add components to `Base` subclass instances, - for example, start with creating an `ExtensionModule` instance. - Components can be added with `.add(component, label=None)` method. - Note that some components (dependencies) may be added - in `.initialize()` method that is called by the constructor - of the `Base` subclass. - -- generate code by calling the `.generate()` method. - -- compile and build an extension module using the generated code. - ExtGen provides a way to do it via `.build()` method - of the `ExtensionModule` instance. Calling this method - will generate extension module, compiles it and returns the - corresponding extension module instance. - -These steps will be discussed in more detail below. - -The `.components` attribute is a list object that contains instances -of `Base` subclasses (components). For instance, the `PyCFunction` instance -defined in the Hello example above, is a component of -`ExtensionModule` instances after calling `.add()` method. Similarly, -the C statement `'printf("Hello!\\n");'` is a component of -`PyCFunction` instance after calling the `.add()` method. - -The `.template` attribute is a string containing an template -to a code idiom. Such an template may contain string replacements -names that are replaced with code idioms generated by the components ---- template evaluation. -If the class should have more than one template then redefine -`.get_templates()` method that should return a tuple of templates. - -The `.containers` attribute is a mapping between a replacement name -(container label) used in template strings and a `Container` instance -holding code idioms from component generation process. The mapping -`.containers` is updated by the `.init_containers()` and -`.update_containers()` methods. These methods should use -`.get_container(<container label>)` to inquire container instances -and `Container.add(<code idiom string>, label=None)` method to add -new code idioms to containers. - -The `.generate()` method will call `.init_containers()` method, the -`.generate()` methods of components, and `.update_containers()` method -to generate code idioms and save the results to the corresponding -containers. Finally, it returns the results of applying -`.evaluate(<string>)` method to templates which replaces the -replacement names with code idioms from containers as well as string -valued attributes of the given `Base` subclass instance. One can set -attributes inside `.initilize()` method. +There are two important concepts in ExtGen model: components and +containers. Components (ref. class `Component`) define code blocks or +code idioms used in building up a code sources. Containers (ref. class +`Container`) are named string lists that are joined together with +specified rules resulting actual code sources. ExtGen uses two steps +for constructing code sources: + +- creating code components and adding them together to a parent + component. For example, the `ExtensionModule` instance in the + hello example becomes a parent component to a `PyCFunction` instance + after executing `m += f`. + +- generating code source by calling `.generate()` method of the + parent component. + +One can iterate the above process as one wishes. + +The method `ExtensionModule.build()` is defined for convenience. +It compiles the generated sources, builds an extension module, +imports the resulting module to Python, and returns the module object. + +All component classes must be derived from the base class `Component` +defined in `extgen/base.py` file. `Component` class defines the +following methods and attributes: + +- `.initialize(self, *args, **kws)` is used to initialize the attributes + and subcomponents of the `Component` instance. Derived classes + usually redefine it to define the signature of the component + constructor. + +- `.add(self, component, container_label=None)` is used to add + subcomponents to the `Component`. Derived classes can affect + the behavior of the `.add()` method by redefining the following + class attributes: + + - `.default_component_class_name` is used when the `component` + argument is not a `Component` instance. + + - `.default_container_label` is used when component + `container_label` is undefined. + + - `.component_containe_map` is used to find `container_label` + corresponding to `component` argument class. + +- `.generate(self)` returns a source code string. It recursively + processes all subcomponents, creates code containers, and + evaluates code templates. + +- `.provides(self)` property method returns an unique string + labeling the current component. The label is used to name + the result of `.generate()` method when storing it to a container. + The result is saved to container only if container does not + contain the given provides label. With this feature one avoids + redefining the same functions, variables, types etc that are needed + by different components. + +- `.init_containers(self)` is called before processing subcomponents. + Derived classes may redefine it. + +- `.update_containers(self)` is called after processing subcomponents. + Derived classes usually define it to fill up any containers. + +- `.get_templates(self)` is used by `.generate()` method to evaluate + the templates and return results. By default, `.get_templates()` + returns `.template` attribute. Derived classes may redefine it + to return a tuple of templates, then also `.generate()` will + return a tuple of source code strings. + +- `.get_container(self, name)` or `.container_<name>` can be used + to retrive a container with a given name. If the current component + does not have requested container then the method tries to find + the container from parent classes. If it still does not find it, + then a new container with the given name will be created for + the current component. One should acctually avoid the last + solution and always define the containers in `.container_options` + class attribute. This attribute is a mapping between container + names and keyword options to the `Container` constructor. + See `Container` options below for more detail. + +- `.evaluate(self, template)` will evaluate `template` using + the attributes (with string values) and the code from containers. + +- `.info(message)`, `.warning(message)` are utility methods and + will write messages to `sys.stderr`. + +- `.register(*components)` will register predefined components + that can be retrived via `.get(provides)` method. + +Deriving a new `Component` class involves the following +tasks: + +- A component class must have a base class `Component`. + +- A component class may redefine `.initialize()`, + `.init_containers()`, `.update_containers()`, `.get_templates()` + methods, `.provides()` property method and `.container_options`, + `.component_container_map`, `.default_container_label`, + `.default_component_class_name`, `.template` attributes. + +- In `.initialize()` method one can process constructor options, + set new attributes and add predefined components. + +- In `.init_containers()` and `.update_containers()` methods + one may retrive containers from parents via `.get_container(<name>)` + method or `.container_<name>` attribute and fill them using + `.add()` method of the container. + +- The attribute `.template` is a string containing formatting mapping keys + that correspond to containers names or instance attribute names. + +- The attribute `.container_options` is a mapping of container + names and keyword argument dictionaries used as options + to a `Container` constructor. + +- The attribute `.component_container_map` is a mapping between + subcomponent class names and the names of containers that should + be used to save the code generation results. + +- All classes derived from `Component` are available as + `Component.<subclass name>`. Here follows a simplified version of `ExtensionModule.template`:: @@ -122,9 +184,12 @@ Here follows a simplified version of `ExtensionModule.template`:: } return; } - -Here `Header`, `TypeDef`, etc are the labels of containers which will be replaced -during evaluation of templates. + +Here formatting mapping keys `Header`, `TypeDef`, etc are the labels +of containers which will be used in the templare evaluation. +See `extgen/*.py` files for more examples how to redefine `Component` +class methods and attributes. + Using `Container` class ======================= @@ -142,21 +207,40 @@ Using `Container` class - `use_firstline_indent=False` - `indent_offset=0` - `user_defined_str=None` + - `replace_map={}` -that can be used to change the behaviour of `Container.__str__()` -method. By default, `Container.__str__()` method returns +that are used to enhance the behaviour of `Container.__str__()` +method. By default, `Container.__str__()` returns `prefix+separator.join(<Container instance>.list)+suffix`. One can add items to `Container` instance using `.add(<string>, -label=None)` method. Here `label` should contain an unique value that -represents the content of `<string>`. If `label` is `None` then -`label = time.time()` will be set. +label=None)` method. The items are saved in `.list` and `.label_map` +attributes. + +`Container` instances can be combined using `+` operator and +copied with `.copy()` method. The `.copy()` method has the +same arguments as `Container` constructor and can be used +to change certain container properties. + +The `label` argument should contain an unique value that represents +the content of `<string>`. If `label` is `None` then `label = +time.time()` will be set. If one tries to add items with the same label to the container then the equality of the corresponding string values will be checked. If they are not equal then `ValueError` is raised, otherwise adding an item is ignored. +If `reverse` is `True` then the `.list` is reversed before joining +its items. If `use_indent` is `True` then each item in `.list` will +be prefixed with `indent_offset` spaces. If `use_firstline_indent` is +`True` then additional indention of the number of starting spaces +in `.line[0]` is used. The `replace_map` is used to apply +`.replace(key, value)` method to the result of `__str__()`. +Full control over the `__str__()` method is obtained via +defining `user_defined_str` that should be a callable object taking +list as input and return a string. + Component classes ================= @@ -165,10 +249,11 @@ ExtGen package defines the following extension module component classes: - `ExtensionModule(<modulename>, *components, numpy=False, provides=.., title=.., description=..)` --- - represents an extension module, + represents an extension module component. If `numpy` is `True` then + `NumPy` support will be added to an extension module. - `PyCFunction(<name>, *components, provides=.., title=.., description=..)` --- - represents an extension function. + represents an extension function component. - `PyCArgument(<name>, *components, provides=.., input_intent=.., output_intent=.., input_title=.., input_description=.., @@ -177,5 +262,39 @@ ExtGen package defines the following extension module component classes: `output_intent` may have values `'required'`, `'optional'`, `'extra'`, `'hide'` and `'hide'`, `'return'`, respectively. - - `CCode(*lines, provides=..)` --- represents any C code block or statement. + - `CCode(*lines, provides=..)` --- represents any C code block or + statement component. + + - `CType(<name>)` --- represents a predefined or intrinsic C type + with a given name. + + - `CTypeAlias(<name>, <ctype>)` --- represents `typedef ctype name;` + declaration. + + - `CTypeFuncAlias(<name>, <return ctype>, <argument ctypes>)` --- + represents `typedef rctype (*name)(actype1,..)` declaration. + Use `.add()` method to add more argument types. + + - `CTypePtr(<ctype>)` --- represents `typedef ctype* ctype_ptr;` + declaration. + + - `CTypeStruct(<name>, *declarations)` --- represents `typedef struct { + <declarations> } name;` declaration. Use `.add()` method to add + more declarations. + + - `CDecl(<ctype>, *names)` --- represents `ctype name1, name2, ..;` + declaration. Use `.add()` method to add more names. + + +Predefined components +===================== + +ExtGen defines the following components that can be retrived +using `Component.get(<provides>)` method: + +- `'Python.h'` - include `Pyhton.h` header file. + +- `'arrayobject.h'` - include NumPy header files. +- `'import_array'` - code for importing numpy package to extension + module. diff --git a/numpy/f2py/lib/extgen/extension_module.py b/numpy/f2py/lib/extgen/extension_module.py index c48f682fd..4b4b2dd29 100644 --- a/numpy/f2py/lib/extgen/extension_module.py +++ b/numpy/f2py/lib/extgen/extension_module.py @@ -1,7 +1,7 @@ -from base import Base +from base import Component -class ExtensionModule(Base): +class ExtensionModule(Component): """ ExtensionModule(<modulename>, *components, numpy=False, provides=..) @@ -59,15 +59,15 @@ class ExtensionModule(Base): ModuleTitle = dict(default='<KILLLINE>',prefix='"\\n\\n',suffix='"',separator='\\n"\n" ', skip_prefix_when_empty=True, skip_suffix_when_empty=True, - use_firstline_indent=True), + use_firstline_indent=True, replace_map = {'\n':'\\n'}), ModuleDescr = dict(default='<KILLLINE>',prefix='"\\n\\nDescription:\\n"\n" ', suffix='"',separator='\\n"\n" ', skip_prefix_when_empty=True, skip_suffix_when_empty=True, - use_firstline_indent=True), + use_firstline_indent=True, replace_map={'\n':'\\n'}), ModuleFuncDoc = dict(default='<KILLLINE>', prefix='"\\n\\nFunctions:\\n"\n" ', separator='\\n"\n" ', suffix='"', skip_prefix_when_empty=True, skip_suffix_when_empty=True, - use_firstline_indent=True), + use_firstline_indent=True, replace_map={'\n':'\\n'}), ) component_container_map = dict(PyCFunction = 'CAPICode') @@ -131,30 +131,31 @@ capi_error: def initialize(self, modulename, *components, **options): self.modulename = modulename - self._provides = options.get('provides', + self._provides = options.pop('provides', '%s_%s' % (self.__class__.__name__, modulename)) + + self.title = options.pop('title', None) + self.description = options.pop('description', None) + + if options: self.warning('%s unused options: %s\n' % (self.__class__.__name__, options)) + # all Python extension modules require Python.h - self.add(Base.get('Python.h'), 'Header') + self.add(Component.get('Python.h'), 'Header') if options.get('numpy'): - self.add(Base.get('arrayobject.h'), 'Header') - self.add(Base.get('import_array'), 'ModuleInit') - - self.title = options.get('title') - self.description = options.get('description') - + self.add(Component.get('arrayobject.h'), 'Header') + self.add(Component.get('import_array'), 'ModuleInit') map(self.add, components) return def update_containers(self): if self.title is not None: - self.container_ModuleTitle += self.title.replace('\n','\\n') + self.container_ModuleTitle += self.title if self.description is not None: - self.container_ModuleDescr += self.description.replace('\n','\\n') + self.container_ModuleDescr += self.description def build(self): import os import sys - import subprocess extfile = self.generate() srcfile = os.path.abspath('%smodule.c' % (self.modulename)) f = open(srcfile, 'w') @@ -181,8 +182,6 @@ if __name__ == '__main__': build_dir = '.' from numpy.distutils.exec_command import exec_command status, output = exec_command(setup_cmd) - #p = subprocess.Popen(setup_cmd, cwd=build_dir, shell=True, stdout=subprocess.PIPE,stderr=subprocess.PIPE) - #sts = os.waitpid(p.pid, 0) if status: raise "Failed to build (status=%s)." % (`status`) exec 'import %s as m' % (modulename) diff --git a/numpy/f2py/lib/extgen/predefined_components.py b/numpy/f2py/lib/extgen/predefined_components.py index e177ac4b5..a24a9fec4 100644 --- a/numpy/f2py/lib/extgen/predefined_components.py +++ b/numpy/f2py/lib/extgen/predefined_components.py @@ -1,8 +1,8 @@ -from base import Base +from base import Component from c_code import CCode -Base.register( +Component.register( CCode('#include "Python.h"', provides='Python.h'), diff --git a/numpy/f2py/lib/extgen/pyc_argument.py b/numpy/f2py/lib/extgen/pyc_argument.py index e3ad5dae4..d88a4c0d3 100644 --- a/numpy/f2py/lib/extgen/pyc_argument.py +++ b/numpy/f2py/lib/extgen/pyc_argument.py @@ -1,7 +1,7 @@ -from base import Base +from base import Component -class PyCArgument(Base): +class PyCArgument(Component): """ PyCArgument(<name>, *components, provides=.., @@ -19,19 +19,23 @@ class PyCArgument(Base): def initialize(self, name, *components, **options): self.name = name - self._provides = options.get('provides', + self._provides = options.pop('provides', '%s_%s' % (self.__class__.__name__, name)) - self.input_intent = options.get('input_intent','required') # 'optional', 'extra', 'hide' - self.output_intent = options.get('output_intent','hide') # 'return' - self.input_title = options.get('input_title', None) - self.output_title = options.get('output_title', None) - self.input_description = options.get('input_description', None) - self.output_description = options.get('output_description', None) + self.input_intent = options.pop('input_intent','required') # 'optional', 'extra', 'hide' + self.output_intent = options.pop('output_intent','hide') # 'return' + self.input_title = options.pop('input_title', None) + self.output_title = options.pop('output_title', None) + self.input_description = options.pop('input_description', None) + self.output_description = options.pop('output_description', None) + + if options: self.warning('%s unused options: %s\n' % (self.__class__.__name__, options)) map(self.add, components) def get_ctype(self): - # scan components for c types + for component, container_label in self.components: + if isinstance(component, Component.CTypeBase): + return component return def init_containers(self): diff --git a/numpy/f2py/lib/extgen/pyc_function.py b/numpy/f2py/lib/extgen/pyc_function.py index 02a2ebb02..daa5a0b94 100644 --- a/numpy/f2py/lib/extgen/pyc_function.py +++ b/numpy/f2py/lib/extgen/pyc_function.py @@ -1,7 +1,7 @@ -from base import Base +from base import Component -class PyCFunction(Base): +class PyCFunction(Component): """ PyCFunction(<name>, *components, provides=..,title=.., description=..) @@ -66,27 +66,27 @@ class PyCFunction(Base): FuncTitle = dict(default='<KILLLINE>',prefix='"\\n\\n',suffix='"',separator='\\n"\n" ', skip_prefix_when_empty=True, skip_suffix_when_empty=True, - use_firstline_indent=True), + use_firstline_indent=True, replace_map={'\n':'\\n'}), FuncDescr = dict(default='<KILLLINE>',prefix='"\\n\\nDescription:\\n"\n" ', suffix='"',separator='\\n"\n" ', skip_prefix_when_empty=True, skip_suffix_when_empty=True, - use_firstline_indent=True), + use_firstline_indent=True, replace_map={'\n':'\\n'}), ReqArgsDoc = dict(default='<KILLLINE>', prefix='"\\n\\nRequired arguments:\\n"\n" ', separator='\\n"\n" ', suffix='"', skip_prefix_when_empty=True, skip_suffix_when_empty=True, - use_firstline_indent=True), + use_firstline_indent=True, replace_map={'\n':'\\n'}), OptArgsDoc = dict(default='<KILLLINE>', prefix='"\\n\\nOptional arguments:\\n"\n" ', separator='\\n"\n" ', suffix='"', skip_prefix_when_empty=True, skip_suffix_when_empty=True, - use_firstline_indent=True), + use_firstline_indent=True, replace_map={'\n':'\\n'}), ExtArgsDoc = dict(default='<KILLLINE>', prefix='"\\n\\nExtra optional arguments:\\n"\n" ', separator='\\n"\n" ', suffix='"', skip_prefix_when_empty=True, skip_suffix_when_empty=True, - use_firstline_indent=True), + use_firstline_indent=True, replace_map={'\n':'\\n'}), RetDoc = dict(default='"Return value:\\n None\\n"', prefix='"\\n\\nReturn values:\\n"\n" ', separator='\\n"\n" ', suffix='"', skip_prefix_when_empty=True, skip_suffix_when_empty=True, - use_firstline_indent=True), + use_firstline_indent=True, replace_map={'\n':'\\n'}), Decl = dict(default='<KILLLINE>', use_indent=True), @@ -155,10 +155,13 @@ static PyObject* def initialize(self, name, *components, **options): self.name = name self.pyc_name = 'pyc_function_'+name - self._provides = options.get('provides', + self._provides = options.pop('provides', '%s_%s' % (self.__class__.__name__, name)) - self.title = options.get('title', None) - self.description = options.get('description', None) + self.title = options.pop('title', None) + self.description = options.pop('description', None) + + if options: self.warning('%s unused options: %s\n' % (self.__class__.__name__, options)) + map(self.add, components) def init_containers(self): @@ -185,10 +188,10 @@ static PyObject* OptExtArgs += OptArgs + ExtArgs ModuleFuncDoc += evaluate('%(name)s(%(ReqArgs)s%(OptExtArgs)s) -> %(RetArgs)s') if self.title is not None: - FuncTitle += self.title.replace('\n','\\n') - ModuleFuncDoc += ' ' + self.title.replace('\n','\\n') + FuncTitle += self.title + ModuleFuncDoc += ' ' + self.title if self.description is not None: - FuncDescr += self.description.replace('\n','\\n') + FuncDescr += self.description return |