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         ir.merge(IR.load(file))
113      return ir
114
115   def output_and_return_ir(self):
116      """writes output if the 'output' attribute is set, then returns"""
117      output = getattr(self, 'output', None)
118      if output:
119         self.ir.save(output)
120      return self.ir
121
122   def process(self, ir, **kwds):
123      """The process method provides the interface to be implemented by subclasses.
124      
125      Commonly used arguments are 'input' and 'output'. If 'input' is defined,
126      it is interpreted as one or more input file names. If 'output' is defined, it
127      is interpreted as an output file (or directory) name.
128      This implementation may serve as a template for real processors."""
129
130      # override default parameter values
131      self.set_parameters(kwds)
132      # merge in IR from 'input' parameter if given
133      self.ir = self.merge_input(ir)
134
135      # do the real work here...
136
137      # write to output (if given) and return IR
138      return self.output_and_return_ir()
139
140class Composite(Processor):
141   """A Composite processor."""
142
143   processors = Parameter([], 'the list of processors this is composed of')
144
145   def __init__(self, *processors, **kwds):
146      """This __init__ is a convenience constructor that takes a var list
147      to list the desired processors. If the named values contain 'processors',
148      they override the var list."""
149      if processors: self.processors = processors
150      self.set_parameters(kwds)
151
152   def process(self, ir, **kwds):
153      """apply a list of processors. The 'input' value is passed to the first
154      processor only, the 'output' to the last. 'verbose' and 'debug' are
155      passed down if explicitely given as named values.
156      All other keywords are ignored."""
157
158      if not self.processors:
159         return super(Composite, self).process(ir, **kwds)
160
161      self.set_parameters(kwds)
162
163      if len(self.processors) == 1:
164         my_kwds = {}
165         if self.input: my_kwds['input'] = self.input
166         if self.output: my_kwds['output'] = self.output
167         if self.verbose: my_kwds['verbose'] = self.verbose
168         if self.debug: my_kwds['debug'] = self.debug
169         if self.profile: my_kwds['profile'] = self.profile
170         return self.processors[0].process(ir, **my_kwds)
171
172      # more than one processor...
173      # call the first, passing the 'input' parameter, if present
174      my_kwds = {}
175      if self.input: my_kwds['input'] = self.input
176      if self.verbose: my_kwds['verbose'] = self.verbose
177      if self.debug: my_kwds['debug'] = self.debug
178      if self.profile: my_kwds['profile'] = self.profile
179      ir = self.processors[0].process(ir, **my_kwds)
180
181      # deal with all between the first and the last;
182      # they only get 'verbose', 'debug', and 'profile' flags
183      my_kwds = {}
184      if self.verbose: my_kwds['verbose'] = self.verbose
185      if self.debug: my_kwds['debug'] = self.debug
186      if self.profile: my_kwds['profile'] = self.profile
187      if len(self.processors) > 2:
188         for p in self.processors[1:-1]:
189            ir = p.process(ir, **my_kwds)
190
191      # call the last, passing the 'output' parameter, if present
192      if self.output: my_kwds['output'] = self.output
193      ir = self.processors[-1].process(ir, **my_kwds)
194
195      return ir
196
197class Store(Processor):
198   """Store is a convenience class useful to write out the intermediate
199   state of the IR within a pipeline such as represented by the 'Composite'"""
200
201   def process(self, ir, **kwds):
202      """Simply store the current IR in the 'output' file."""
203
204      self.set_parameters(kwds)
205      self.ir = self.merge_input(ir)
206      return self.output_and_return_ir()
207