Synopsis - Synopsis/Processors/Comments/Grouper.py

File: Synopsis/Processors/Comments/Grouper.py

      1#
      2# Copyright (C) 2005 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
      8from Synopsis import ASG
      9from Synopsis.QualifiedName import QualifiedName
     10from Synopsis.Processors.Transformer import Transformer
     11import re
     12
     13class Grouper(Transformer):
     14    """A class that detects grouping tags and moves the enclosed nodes
     15    into a subnode (a 'Group')"""
     16
     17    tags = r'^\s*((?P<open>@group\s*(?P<name>.*){)|(?P<close>\s*}))\s*\Z'
     18
     19    def __init__(self, **kwds):
     20
     21        Transformer.__init__(self, **kwds)
     22        self.__group_stack = [[]]
     23        self.tags = re.compile(Grouper.tags, re.M)
     24
     25    def strip_dangling_groups(self):
     26        """As groups must not overlap with 'real' scopes,
     27        make sure all groups created in the current scope are closed
     28        when leaving the scope."""
     29
     30        if self.__group_stack[-1]:
     31            print 'Warning: group stack is non-empty !'
     32            while (self.__group_stack[-1]):
     33                group = self.__group_stack[-1][-1]
     34                print 'forcing closing of group %s (opened near %s:%d)'%(group.name, group.file.name, group.line)
     35                self.pop_group()
     36
     37    def finalize(self):
     38        """replace the ASG with the newly created one"""
     39
     40        self.strip_dangling_groups()
     41        super(Grouper, self).finalize()
     42
     43    def push(self):
     44        """starts a new group stack to be able to validate group scopes"""
     45
     46        Transformer.push(self)
     47        self.__group_stack.append([])
     48
     49    def pop(self, decl):
     50        """Make sure the current group stack is empty."""
     51
     52        self.strip_dangling_groups()
     53        self.__group_stack.pop()
     54        Transformer.pop(self, decl)
     55
     56    def push_group(self, group):
     57        """Push new group scope to the stack."""
     58
     59        self.__group_stack[-1].append(group)
     60        Transformer.push(self)
     61
     62    def pop_group(self, decl=None):
     63        """Pop a group scope from the stack.
     64
     65        decl -- an optional declaration from which to extract the context,
     66        used for the error message if needed.
     67        """
     68
     69        if self.__group_stack[-1]:
     70            group = self.__group_stack[-1].pop()
     71            group.declarations = self.current_scope()
     72            Transformer.pop(self, group)
     73        else:
     74            if decl:
     75                print "Warning: no group open in current scope (near %s:%d), ignoring."%(decl.file.name, decl.line)
     76            else:
     77                print "Warning: no group open in current scope, ignoring."
     78
     79
     80    def process_comments(self, decl):
     81        """Checks for grouping tags.
     82        If an opening tag is found in the middle of a comment, a new Group is generated, the preceeding
     83        comments are associated with it, and is pushed onto the scope stack as well as the groups stack.
     84        """
     85
     86        comments = []
     87        for c in decl.annotations.get('comments', []):
     88            if c is None:
     89                comments.append(None)
     90                continue
     91            tag = self.tags.search(c)
     92            if not tag:
     93                comments.append(c)
     94                continue
     95            elif tag.group('open'):
     96
     97                if self.debug:
     98                    print 'found group open tag in', decl.name
     99
    100                # Open the group. <name> is remainder of line.
    101                label = tag.group('name') or 'unnamed'
    102                label = label.strip()
    103                # The comment before the open marker becomes the group comment.
    104                if tag.start('open') > 0:
    105                    c = c[:tag.start('open')]
    106                    comments.append(c)
    107                group = ASG.Group(decl.file, decl.line, 'group', QualifiedName((label,)))
    108                group.annotations['comments'] = comments
    109                comments = []
    110                self.push_group(group)
    111
    112            elif tag.group('close'):
    113
    114                if self.debug:
    115                    print 'found group close tag in', decl.name
    116
    117                self.pop_group(decl)
    118
    119        decl.annotations['comments'] = comments
    120
    121
    122    def visit_declaration(self, decl):
    123
    124        self.process_comments(decl)
    125        self.add(decl)
    126
    127    def visit_scope(self, scope):
    128        """Visits all children of the scope in a new scope. The value of
    129        current_scope() at the end of the list is used to replace scope's list of
    130        declarations - hence you can remove (or insert) declarations from the
    131        list."""
    132
    133        self.process_comments(scope)
    134        self.push()
    135        for d in scope.declarations: d.accept(self)
    136        scope.declarations = self.current_scope()
    137        self.pop(scope)
    138
    139    def visit_enum(self, enum):
    140        """Does the same as visit_scope, but for the enum's list of
    141        enumerators"""
    142
    143        self.process_comments(enum)
    144        self.push()
    145        for enumor in enum.enumerators:
    146            enumor.accept(self)
    147        enum.enumerators = self.current_scope()
    148        self.pop(enum)
    149
    150    def visit_enumerator(self, enumor):
    151        """Removes dummy enumerators"""
    152
    153        if enumor.type == "dummy": return #This wont work since Core.ASG.Enumerator forces type to "enumerator"
    154        if not len(enumor.name): return # workaround.
    155        self.add(enumor)
    156
    157

Generated on Thu Apr 16 16:27:16 2009 by
synopsis (version devel)