[Numpy-svn] r3946 - trunk/numpy/f2py/lib/extgen
numpy-svn at scipy.org
numpy-svn at scipy.org
Sun Aug 5 11:23:47 EDT 2007
Author: pearu
Date: 2007-08-05 10:23:30 -0500 (Sun, 05 Aug 2007)
New Revision: 3946
Modified:
trunk/numpy/f2py/lib/extgen/__init__.py
trunk/numpy/f2py/lib/extgen/base.py
trunk/numpy/f2py/lib/extgen/c_code.py
trunk/numpy/f2py/lib/extgen/c_type.py
trunk/numpy/f2py/lib/extgen/doc.txt
trunk/numpy/f2py/lib/extgen/extension_module.py
trunk/numpy/f2py/lib/extgen/predefined_components.py
trunk/numpy/f2py/lib/extgen/pyc_argument.py
trunk/numpy/f2py/lib/extgen/pyc_function.py
Log:
Impl CType classes and rewrote docs.
Modified: trunk/numpy/f2py/lib/extgen/__init__.py
===================================================================
--- trunk/numpy/f2py/lib/extgen/__init__.py 2007-08-04 21:03:20 UTC (rev 3945)
+++ trunk/numpy/f2py/lib/extgen/__init__.py 2007-08-05 15:23:30 UTC (rev 3946)
@@ -11,4 +11,8 @@
from pyc_argument import PyCArgument
from c_code import CCode
+import c_type
+from c_type import *
+__all__ += c_type.__all__
+
import predefined_components
Modified: trunk/numpy/f2py/lib/extgen/base.py
===================================================================
--- trunk/numpy/f2py/lib/extgen/base.py 2007-08-04 21:03:20 UTC (rev 3945)
+++ trunk/numpy/f2py/lib/extgen/base.py 2007-08-05 15:23:30 UTC (rev 3946)
@@ -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 @@
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
+ def __add__(self, other): # convenience method
+ self.add(other)
+ return self
+ __iadd__ = __add__
- # 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
-
- @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.
+ def add(self, component, container_label=None):
"""
- if self._provides is None:
- return '%s_%s' % (self.__class__.__name__, id(self))
- return self._provides
-
- def get_templates(self):
+ Append component and its target container label to components list.
"""
- Return instance templates.
- """
- 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 @@
# 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 @@
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,33 +197,42 @@
# 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))
+ return self.template
- @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)
-
def evaluate(self, template, **attrs):
"""
Evaluate template using instance attributes and code
@@ -235,8 +257,9 @@
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 = {}
@staticmethod
@@ -245,11 +268,11 @@
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 @@
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 @@
def numpy_version(self):
import numpy
return numpy.__version__
+
class Container(object):
"""
@@ -292,7 +316,7 @@
"hey, hoo, bar"
"""
- __metaclass__ = BaseMetaClass
+ __metaclass__ = ComponentMetaClass
def __init__(self,
separator='\n', prefix='', suffix='',
@@ -303,6 +327,7 @@
use_indent = False,
indent_offset = 0,
use_firstline_indent = False, # implies use_indent
+ replace_map = {}
):
self.list = []
self.label_map = {}
@@ -318,8 +343,9 @@
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 @@
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 @@
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)
Modified: trunk/numpy/f2py/lib/extgen/c_code.py
===================================================================
--- trunk/numpy/f2py/lib/extgen/c_code.py 2007-08-04 21:03:20 UTC (rev 3945)
+++ trunk/numpy/f2py/lib/extgen/c_code.py 2007-08-05 15:23:30 UTC (rev 3946)
@@ -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 @@
assert label is None,`label`
self.lines.extend(component.lines)
else:
- Base.add(self, component. label)
+ Component.add(self, component. label)
Modified: trunk/numpy/f2py/lib/extgen/c_type.py
===================================================================
--- trunk/numpy/f2py/lib/extgen/c_type.py 2007-08-04 21:03:20 UTC (rev 3945)
+++ trunk/numpy/f2py/lib/extgen/c_type.py 2007-08-05 15:23:30 UTC (rev 3946)
@@ -1,137 +1,200 @@
"""
-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
+from base import Component
-class CTypeBase(Base):
+class CTypeBase(Component):
- def declare(self, name):
- return '%s %s;' % (self.typename, name)
+ template = '%(name)s'
+ template_typedef = ''
+ default_container_label = '<IGNORE>'
+ default_component_class_name = 'CType'
+ @property
+ def provides(self):
+ return '%s_%s' % (self.__class__.__name__, self.name)
+
+ def initialize(self, name, *components):
+ self.name = name
+ map(self.add, components)
+
+ def update_containers(self):
+ self.container_TypeDef += self.evaluate(self.template_typedef)
+
def __str__(self):
- return self.typename
+ return self.name
- def asPtr(self):
- return CTypePtr(self)
+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)
+class CType(CTypeBase):
+
+ """ CType(<name>)
+
+ Represents any predefined type in C.
+
+ >>> cint = CType('int')
+ >>> print cint
+ int
+ >>> _CatchTypeDef(cint).generate()
+ ''
+ """
+
+ def initialize(self, name):
+ self.name = name
+
+ def update_containers(self):
+ pass
+
class CTypeAlias(CTypeBase):
- def __new__(cls, typename, ctype):
- obj = Base.__new__(cls)
- assert isinstance(ctype, CTypeBase),`type(ctype)`
- obj.add(typename, ctype)
- return obj
+ """ CTypeAlias(<name>, <ctype>)
- @property
- def typename(self): return self.components[0][0]
- @property
- def ctype(self): return self.components[0][1]
+ >>> aint = CTypeAlias('aint', 'int')
+ >>> print aint
+ aint
+ >>> print _CatchTypeDef(aint).generate()
+ typedef int aint;
+ """
- 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)
+ 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 CTypeFunction(CTypeBase):
+class CTypeFuncAlias(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
+ """
+ CTypeFuncAlias(<name>, <return ctype>, *(<argument ctypes>))
- @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:]]
+ >>> 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);
+ """
- 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)
+ 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:])
+
+class CTypePtr(CTypeBase):
+
+ """
+ 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 CTypeStruct(CTypeBase):
- 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
+ """
+ CTypeStruct(<name>, *(<declarations>))
- @property
- def typename(self): return self._args[0]
+ >>> 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;
- 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 CTypePtr(CTypeBase):
+ container_options = dict(Decl = dict(default='<KILLLINE>', use_indent=True))
+ default_component_class_name = None #'CDecl'
+ component_container_map = dict(CDecl='Decl')
- def __new__(cls, ctype):
- obj = Base.__new__(cls)
- assert isinstance(ctype, CTypeBase),`type(ctype)`
- obj.add('*', ctype)
- return obj
+ template_typedef = '''\
+typedef struct {
+ %(Decl)s
+} %(name)s;'''
- @property
- def ctype(self): return self.components[0][1]
-
- @property
- def typename(self):
- return self.ctype.typename + '*'
+ def initialize(self, name, *components):
+ self.name = name
+ map(self.add, components)
- def local_generate(self, params=None):
- return self.declare(params)
+class CDecl(Component):
-class CTypeDefined(CTypeBase):
+ """
+ CDecl(<ctype>, *(<names with or without initialization>))
- @property
- def typename(self): return self._args[0]
+ >>> 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;
+ """
-class CTypeIntrinsic(CTypeDefined):
+ 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 __new__(cls, typename):
- return Base.__new__(cls, typename)
+ def initialize(self, ctype, *names):
+ if isinstance(ctype, str): ctype = CType(ctype)
+ self.add(ctype, 'CTypeName')
+ map(self.add, names)
-class CPyObject(CTypeDefined):
- def __new__(cls):
- return Base.__new__(cls, 'PyObject')
-class CInt(CTypeIntrinsic):
+if 0:
- def __new__(cls):
- return Base.__new__(cls, 'int')
-
def local_generate(self, params=None):
container = self.get_container('CAPICode')
code = '''\
@@ -154,3 +217,9 @@
return self.declare(params)
+def _test():
+ import doctest
+ doctest.testmod()
+
+if __name__ == "__main__":
+ _test()
Modified: trunk/numpy/f2py/lib/extgen/doc.txt
===================================================================
--- trunk/numpy/f2py/lib/extgen/doc.txt 2007-08-04 21:03:20 UTC (rev 3945)
+++ trunk/numpy/f2py/lib/extgen/doc.txt 2007-08-05 15:23:30 UTC (rev 3946)
@@ -22,78 +22,140 @@
>>> 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()`.
+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 an extension module is carried out by the following steps:
+- 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`.
-- 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.
+- generating code source by calling `.generate()` method of the
+ parent component.
-- generate code by calling the `.generate()` method.
+One can iterate the above process as one wishes.
-- 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.
+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.
-These steps will be discussed in more detail below.
+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:
-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.
+- `.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.
-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.
+- `.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:
-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.
+ - `.default_component_class_name` is used when the `component`
+ argument is not a `Component` instance.
-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.
+ - `.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`::
#include "Python.h"
@@ -122,10 +184,13 @@
}
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,22 +207,41 @@
- `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 @@
- `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 @@
`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.
Modified: trunk/numpy/f2py/lib/extgen/extension_module.py
===================================================================
--- trunk/numpy/f2py/lib/extgen/extension_module.py 2007-08-04 21:03:20 UTC (rev 3945)
+++ trunk/numpy/f2py/lib/extgen/extension_module.py 2007-08-05 15:23:30 UTC (rev 3946)
@@ -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 @@
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 @@
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 @@
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)
Modified: trunk/numpy/f2py/lib/extgen/predefined_components.py
===================================================================
--- trunk/numpy/f2py/lib/extgen/predefined_components.py 2007-08-04 21:03:20 UTC (rev 3945)
+++ trunk/numpy/f2py/lib/extgen/predefined_components.py 2007-08-05 15:23:30 UTC (rev 3946)
@@ -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'),
Modified: trunk/numpy/f2py/lib/extgen/pyc_argument.py
===================================================================
--- trunk/numpy/f2py/lib/extgen/pyc_argument.py 2007-08-04 21:03:20 UTC (rev 3945)
+++ trunk/numpy/f2py/lib/extgen/pyc_argument.py 2007-08-05 15:23:30 UTC (rev 3946)
@@ -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 @@
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):
Modified: trunk/numpy/f2py/lib/extgen/pyc_function.py
===================================================================
--- trunk/numpy/f2py/lib/extgen/pyc_function.py 2007-08-04 21:03:20 UTC (rev 3945)
+++ trunk/numpy/f2py/lib/extgen/pyc_function.py 2007-08-05 15:23:30 UTC (rev 3946)
@@ -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 @@
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 @@
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 @@
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
More information about the Numpy-svn
mailing list