File: Synopsis/Processor.py
  1#
  2# Copyright (C) 2003 Stefan Seefeld
  3# All rights reserved.
  4# Licensed to the public under the terms of the GNU LGPL (>= 2),
  5# see the file COPYING for details.
  6#
  7
  8import IR
  9
 10class Error(Exception):
 11   """An exception a processor may raise during processing."""
 12
 13   def __init__(self, what):
 14
 15      self.what = what
 16
 17   def __str__(self):
 18      return "%s: %s"%(self.__class__.__name__, self.what)
 19
 20class InvalidArgument(Error): pass
 21class MissingArgument(Error): pass
 22class InvalidCommand(Error): pass
 23class InternalError(Error): pass
 24
 25class Parameter(object):
 26   """A Parameter is a documented value, kept inside a Processor."""
 27   def __init__(self, value, doc):
 28      self.value = value
 29      self.doc = doc
 30
 31class Type(type):
 32   """Type is the Processor's __metaclass__."""
 33   def __init__(cls, name, bases, dict):
 34      """Generate a '_parameters' dictionary holding all the 'Parameter' objects.
 35      Then replace 'Parameter' objects by their values for convenient use inside
 36      the code."""
 37      parameters = {}
 38      for i in dict:
 39         if isinstance(dict[i], Parameter):
 40            parameters[i] = dict[i]
 41      for i in parameters:
 42         setattr(cls, i, dict[i].value)
 43      setattr(cls, '_parameters', parameters)
 44
 45class Parametrized(object):
 46   """Parametrized implements handling of Parameter attributes."""
 47
 48   __metaclass__ = Type
 49
 50   def __new__(cls, *args, **kwds):
 51      """merge all parameter catalogs for easy access to documentation,
 52      then use keyword arguments to override default values."""
 53      instance = object.__new__(cls)
 54      # iterate over all base classes, starting at the 'Parametrized' base class
 55      # i.e. remove mixin classes
 56      hierarchy = list(filter(lambda i:issubclass(i, Parametrized), cls.__mro__))
 57      hierarchy.reverse()
 58      parameters = {}
 59      for c in hierarchy:
 60         parameters.update(c._parameters)
 61      setattr(instance, '_parameters', parameters)
 62
 63      for p in kwds:
 64         if not p in instance._parameters:
 65            raise InvalidArgument('"%s.%s" processor does not have "%s" parameter'
 66                                  %(cls.__module__, cls.__name__, p))
 67         else:
 68            setattr(instance, p, kwds[p])
 69
 70      return instance
 71
 72   def __init__(self, **kwds):
 73      """The constructor uses the keywords to update the parameter list."""
 74
 75      self.set_parameters(kwds)
 76
 77   def clone(self, *args, **kwds):
 78      """Create a copy of this Parametrized.
 79      The only copied attributes are the ones corresponding to parameters."""
 80
 81      new_kwds = dict([(k, getattr(self, k)) for k in self._parameters])
 82      new_kwds.update(kwds)
 83      return type(self)(*args, **new_kwds)
 84
 85
 86   def get_parameters(self):
 87
 88      return self._parameters
 89
 90   def set_parameters(self, kwds):
 91      """Sets the given parameters to override the default values."""
 92      for i in kwds:
 93         if i in self._parameters:
 94            setattr(self, i, kwds[i])
 95         else:
 96            raise InvalidArgument, "No parameter '%s' in '%s'"%(i, self.__class__.__name__)
 97
 98
 99class Processor(Parametrized):
100   """Processor documentation..."""
101
102   verbose = Parameter(False, "operate verbosely")
103   debug = Parameter(False, "generate debug traces")
104   profile = Parameter(False, "output profile data")
105   input = Parameter([], "input files to process")
106   output = Parameter('', "output file to save the ir to")
107
108   def merge_input(self, ir):
109      """Join the given IR with a set of IRs to be read from 'input' parameter"""
110      input = getattr(self, 'input', [])
111      for file in input:
112         try:
113            ir.merge(IR.load(file))
114         except:
115            raise InvalidArgument('unable to load IR from %s'%file)
116      return ir
117
118   def output_and_return_ir(self):
119      """writes output if the 'output' attribute is set, then returns"""
120      output = getattr(self, 'output', None)
121      if output:
122         self.ir.save(output)
123      return self.ir
124
125   def process(self, ir, **kwds):
126      """The process method provides the interface to be implemented by subclasses.
127      
128      Commonly used arguments are 'input' and 'output'. If 'input' is defined,
129      it is interpreted as one or more input file names. If 'output' is defined, it
130      is interpreted as an output file (or directory) name.
131      This implementation may serve as a template for real processors."""
132
133      # override default parameter values
134      self.set_parameters(kwds)
135      # merge in IR from 'input' parameter if given
136      self.ir = self.merge_input(ir)
137
138      # do the real work here...
139
140      # write to output (if given) and return IR
141      return self.output_and_return_ir()
142
143class Composite(Processor):
144   """A Composite processor."""
145
146   processors = Parameter([], 'the list of processors this is composed of')
147
148   def __init__(self, *processors, **kwds):
149      """This __init__ is a convenience constructor that takes a var list
150      to list the desired processors. If the named values contain 'processors',
151      they override the var list."""
152      if processors: self.processors = processors
153      self.set_parameters(kwds)
154
155   def process(self, ir, **kwds):
156      """apply a list of processors. The 'input' value is passed to the first
157      processor only, the 'output' to the last. 'verbose' and 'debug' are
158      passed down if explicitely given as named values.
159      All other keywords are ignored."""
160
161      if not self.processors:
162         return super(Composite, self).process(ir, **kwds)
163
164      self.set_parameters(kwds)
165
166      if len(self.processors) == 1:
167         my_kwds = {}
168         if self.input: my_kwds['input'] = self.input
169         if self.output: my_kwds['output'] = self.output
170         if self.verbose: my_kwds['verbose'] = self.verbose
171         if self.debug: my_kwds['debug'] = self.debug
172         if self.profile: my_kwds['profile'] = self.profile
173         return self.processors[0].process(ir, **my_kwds)
174
175      # more than one processor...
176      # call the first, passing the 'input' parameter, if present
177      my_kwds = {}
178      if self.input: my_kwds['input'] = self.input
179      if self.verbose: my_kwds['verbose'] = self.verbose
180      if self.debug: my_kwds['debug'] = self.debug
181      if self.profile: my_kwds['profile'] = self.profile
182      ir = self.processors[0].process(ir, **my_kwds)
183
184      # deal with all between the first and the last;
185      # they only get 'verbose', 'debug', and 'profile' flags
186      my_kwds = {}
187      if self.verbose: my_kwds['verbose'] = self.verbose
188      if self.debug: my_kwds['debug'] = self.debug
189      if self.profile: my_kwds['profile'] = self.profile
190      if len(self.processors) > 2:
191         for p in self.processors[1:-1]:
192            ir = p.process(ir, **my_kwds)
193
194      # call the last, passing the 'output' parameter, if present
195      if self.output: my_kwds['output'] = self.output
196      ir = self.processors[-1].process(ir, **my_kwds)
197
198      return ir
199
200class Store(Processor):
201   """Store is a convenience class useful to write out the intermediate
202   state of the IR within a pipeline such as represented by the 'Composite'"""
203
204   def process(self, ir, **kwds):
205      """Simply store the current IR in the 'output' file."""
206
207      self.set_parameters(kwds)
208      self.ir = self.merge_input(ir)
209      return self.output_and_return_ir()
210