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