1#!/usr/local/bin/python3.8
2#
3# Author: Jashua R. Cloutier (contact via https://bitbucket.org/senex)
4# Project: http://senexcanis.com/open-source/cppheaderparser/
5#
6# Copyright (C) 2011, Jashua R. Cloutier
7# All rights reserved.
8#
9# Redistribution and use in source and binary forms, with or without
10# modification, are permitted provided that the following conditions
11# are met:
12#
13# * Redistributions of source code must retain the above copyright
14#   notice, this list of conditions and the following disclaimer.
15#
16# * Redistributions in binary form must reproduce the above copyright
17#   notice, this list of conditions and the following disclaimer in
18#   the documentation and/or other materials provided with the
19#   distribution.
20#
21# * Neither the name of Jashua R. Cloutier nor the names of its
22#   contributors may be used to endorse or promote products derived from
23#   this software without specific prior written permission.  Stories,
24#   blog entries etc making reference to this project may mention the
25#   name Jashua R. Cloutier in terms of project originator/creator etc.
26#
27# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
30# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
31# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
32# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
33# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
34# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
35# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
37# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
38# POSSIBILITY OF SUCH DAMAGE.
39#
40#
41# The CppHeaderParser.py script is written in Python 2.4 and released to
42# the open source community for continuous improvements under the BSD
43# 2.0 new license, which can be found at:
44#
45#   http://www.opensource.org/licenses/bsd-license.php
46#
47"""Parse C++ header files and generate a data structure
48representing the class
49"""
50
51import ply.lex as lex
52import os
53import sys
54import re
55
56import inspect
57
58def lineno():
59    """Returns the current line number in our program."""
60    return inspect.currentframe().f_back.f_lineno
61
62version = __version__ = "2.7.4"
63
64tokens = [
65    'NUMBER',
66    'FLOAT_NUMBER',
67    'TEMPLATE_NAME',
68    'NAME',
69    'OPEN_PAREN',
70    'CLOSE_PAREN',
71    'OPEN_BRACE',
72    'CLOSE_BRACE',
73    'OPEN_SQUARE_BRACKET',
74    'CLOSE_SQUARE_BRACKET',
75    'COLON',
76    'SEMI_COLON',
77    'COMMA',
78    'TAB',
79    'BACKSLASH',
80    'PIPE',
81    'PERCENT',
82    'EXCLAMATION',
83    'CARET',
84    'COMMENT_SINGLELINE',
85    'COMMENT_MULTILINE',
86    'PRECOMP_MACRO',
87    'PRECOMP_MACRO_CONT',
88    'ASTERISK',
89    'AMPERSTAND',
90    'EQUALS',
91    'MINUS',
92    'PLUS',
93    'DIVIDE',
94    'CHAR_LITERAL',
95    'STRING_LITERAL',
96    'NEW_LINE',
97    'SQUOTE',
98]
99
100t_ignore = " \r.?@\f"
101t_NUMBER = r'[0-9][0-9XxA-Fa-f]*'
102t_FLOAT_NUMBER = r'[-+]?[0-9]*\.[0-9]+([eE][-+]?[0-9]+)?'
103t_TEMPLATE_NAME = r'CppHeaderParser_template_[0-9]+'
104t_NAME = r'[<>A-Za-z_~][A-Za-z0-9_]*'
105t_OPEN_PAREN = r'\('
106t_CLOSE_PAREN = r'\)'
107t_OPEN_BRACE = r'{'
108t_CLOSE_BRACE = r'}'
109t_OPEN_SQUARE_BRACKET = r'\['
110t_CLOSE_SQUARE_BRACKET = r'\]'
111t_SEMI_COLON = r';'
112t_COLON = r':'
113t_COMMA = r','
114t_TAB = r'\t'
115t_BACKSLASH = r'\\'
116t_PIPE = r'\|'
117t_PERCENT = r'%'
118t_CARET = r'\^'
119t_EXCLAMATION = r'!'
120t_PRECOMP_MACRO = r'\#.*'
121t_PRECOMP_MACRO_CONT = r'.*\\\n'
122def t_COMMENT_SINGLELINE(t):
123    r'\/\/.*\n'
124    global doxygenCommentCache
125    if t.value.startswith("///") or t.value.startswith("//!"):
126        if doxygenCommentCache:
127            doxygenCommentCache += "\n"
128        if t.value.endswith("\n"):
129            doxygenCommentCache += t.value[:-1]
130        else:
131            doxygenCommentCache += t.value
132    t.lexer.lineno += len([a for a in t.value if a=="\n"])
133t_ASTERISK = r'\*'
134t_MINUS = r'\-'
135t_PLUS = r'\+'
136t_DIVIDE = r'/(?!/)'
137t_AMPERSTAND = r'&'
138t_EQUALS = r'='
139t_CHAR_LITERAL = "'.'"
140t_SQUOTE = "'"
141#found at http://wordaligned.org/articles/string-literals-and-regular-expressions
142#TODO: This does not work with the string "bla \" bla"
143t_STRING_LITERAL = r'"([^"\\]|\\.)*"'
144#Found at http://ostermiller.org/findcomment.html
145def t_COMMENT_MULTILINE(t):
146    r'/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/'
147    global doxygenCommentCache
148    if t.value.startswith("/**") or t.value.startswith("/*!"):
149        #not sure why, but get double new lines
150        v = t.value.replace("\n\n", "\n")
151        #strip prefixing whitespace
152        v = re.sub("\n[\s]+\*", "\n*", v)
153        doxygenCommentCache += v
154    t.lexer.lineno += len([a for a in t.value if a=="\n"])
155def t_NEWLINE(t):
156    r'\n+'
157    t.lexer.lineno += len(t.value)
158
159def t_error(v):
160    print(( "Lex error: ", v ))
161
162lex.lex()
163# Controls error_print
164print_errors = 1
165# Controls warning_print
166print_warnings = 1
167# Controls debug_print
168debug = 0
169# Controls trace_print
170debug_trace = 0
171
172def error_print(arg):
173    if print_errors: print(("[%4d] %s"%(inspect.currentframe().f_back.f_lineno, arg)))
174
175def warning_print(arg):
176    if print_warnings: print(("[%4d] %s"%(inspect.currentframe().f_back.f_lineno, arg)))
177
178def debug_print(arg):
179    global debug
180    if debug: print(("[%4d] %s"%(inspect.currentframe().f_back.f_lineno, arg)))
181
182def trace_print(*arg):
183    global debug_trace
184    if debug_trace:
185        sys.stdout.write("[%s] "%(inspect.currentframe().f_back.f_lineno))
186        for a in arg: sys.stdout.write("%s "%a)
187        sys.stdout.write("\n")
188
189supportedAccessSpecifier = [
190    'public',
191    'protected',
192    'private'
193]
194
195#Symbols to ignore, usually special macros
196ignoreSymbols = [
197    'Q_OBJECT',
198]
199
200doxygenCommentCache = ""
201
202#Track what was added in what order and at what depth
203parseHistory = []
204
205def is_namespace(nameStack):
206    """Determines if a namespace is being specified"""
207    if len(nameStack) == 0:
208        return False
209    if nameStack[0] == "namespace":
210        return True
211    return False
212
213def is_enum_namestack(nameStack):
214    """Determines if a namestack is an enum namestack"""
215    if len(nameStack) == 0:
216        return False
217    if nameStack[0] == "enum":
218        return True
219    if len(nameStack) > 1 and nameStack[0] == "typedef" and nameStack[1] == "enum":
220        return True
221    return False
222
223def is_fundamental(s):
224    for a in s.split():
225        if a not in ["size_t", "struct", "union", "unsigned", "signed", "bool", "char", "short", "int", "float", "double", "long", "void", "*"]: return False
226    return True
227
228def is_function_pointer_stack(stack):
229    """Count how many non-nested paranthesis are in the stack.  Useful for determining if a stack is a function pointer"""
230    paren_depth = 0
231    paren_count = 0
232    star_after_first_paren = False
233    last_e = None
234    for e in stack:
235        if e == "(":
236            paren_depth += 1
237        elif e == ")" and paren_depth > 0:
238            paren_depth -= 1
239            if paren_depth == 0:
240                paren_count += 1
241        elif e == "*" and last_e == "(" and paren_count == 0 and paren_depth == 1:
242            star_after_first_paren = True
243        last_e = e
244
245    if star_after_first_paren and paren_count == 2:
246        return True
247    else:
248        return False
249
250def is_method_namestack(stack):
251    r = False
252    if '(' not in stack: r = False
253    elif stack[0] == 'typedef': r = False    # TODO deal with typedef function prototypes
254    #elif '=' in stack and stack.index('=') < stack.index('(') and stack[stack.index('=')-1] != 'operator': r = False    #disabled July6th - allow all operators
255    elif 'operator' in stack: r = True    # allow all operators
256    elif '{' in stack and stack.index('{') < stack.index('('): r = False    # struct that looks like a method/class
257    elif '(' in stack and ')' in stack:
258        if '{' in stack and '}' in stack: r = True
259        elif stack[-1] == ';':
260            if is_function_pointer_stack(stack):
261                r = False
262            else:
263                r = True
264        elif '{' in stack: r = True    # ideally we catch both braces... TODO
265    else: r = False
266    #Test for case of property set to something with parens such as "static const int CONST_A = (1 << 7) - 1;"
267    if r and "(" in stack and "=" in stack and 'operator' not in stack:
268        if stack.index("=") < stack.index("("): r = False
269    return r
270
271def is_property_namestack(nameStack):
272    r = False
273    if '(' not in nameStack and ')' not in nameStack: r = True
274    elif "(" in nameStack and "=" in nameStack and nameStack.index("=") < nameStack.index("("): r = True
275    #See if we are a function pointer
276    if not r and is_function_pointer_stack(nameStack): r = True
277    return r
278
279def detect_lineno(s):
280    """Detect the line number for a given token string"""
281    try:
282        rtn = s.lineno()
283        if rtn != -1:
284            return rtn
285    except: pass
286    global curLine
287    return curLine
288
289def filter_out_attribute_keyword(stack):
290    """Strips __attribute__ and its parenthetical expression from the stack"""
291    if "__attribute__" not in stack: return stack
292    try:
293        debug_print("Stripping __attribute__ from %s"% stack)
294        attr_index = stack.index("__attribute__")
295        attr_end = attr_index + 1 #Assuming not followed by parenthetical expression which wont happen
296        #Find final paren
297        if stack[attr_index + 1] == '(':
298            paren_count = 1
299            for i in range(attr_index + 2, len(stack)):
300                elm = stack[i]
301                if elm == '(':
302                    paren_count += 1
303                elif elm == ')':
304                    paren_count -= 1
305                    if paren_count == 0:
306                        attr_end = i + 1
307                        break
308        new_stack = stack[0:attr_index] + stack[attr_end:]
309        debug_print("stripped stack is %s"% new_stack)
310        return new_stack
311    except:
312        return stack
313
314
315class TagStr(str):
316    """Wrapper for a string that allows us to store the line number associated with it"""
317    lineno_reg = {}
318    def __new__(cls,*args,**kw):
319        new_obj =  str.__new__(cls,*args)
320        if "lineno" in kw:
321            TagStr.lineno_reg[id(new_obj)] = kw["lineno"]
322        return new_obj
323
324    def __del__(self):
325        try:
326            del TagStr.lineno_reg[id(self)]
327        except: pass
328
329    def lineno(self):
330        return TagStr.lineno_reg.get(id(self), -1)
331
332class CppParseError(Exception): pass
333
334class CppClass(dict):
335    """Takes a name stack and turns it into a class
336
337    Contains the following Keys:
338    self['name'] - Name of the class
339    self['doxygen'] - Doxygen comments associated with the class if they exist
340    self['inherits'] - List of Classes that this one inherits where the values
341        are of the form {"access": Anything in supportedAccessSpecifier
342                                  "class": Name of the class
343    self['methods'] - Dictionary where keys are from supportedAccessSpecifier
344        and values are a lists of CppMethod's
345    self['properties'] - Dictionary where keys are from supportedAccessSpecifier
346        and values are lists of CppVariable's
347    self['enums'] - Dictionary where keys are from supportedAccessSpecifier and
348        values are lists of CppEnum's
349    self['structs'] - Dictionary where keys are from supportedAccessSpecifier and
350        values are lists of nested Struct's
351
352    An example of how this could look is as follows:
353    #self =
354    {
355        'name': ""
356        'inherits':[]
357        'methods':
358        {
359            'public':[],
360            'protected':[],
361            'private':[]
362        },
363        'properties':
364        {
365            'public':[],
366            'protected':[],
367            'private':[]
368        },
369        'enums':
370        {
371            'public':[],
372            'protected':[],
373            'private':[]
374        }
375    }
376    """
377
378    def get_all_methods(self):
379        r = []
380        for typ in supportedAccessSpecifier: r += self['methods'][typ]
381        return r
382
383    def get_all_method_names( self ):
384        r = []
385        for typ in supportedAccessSpecifier: r += self.get_method_names(typ)        # returns list
386        return r
387
388    def get_all_pure_virtual_methods( self ):
389        r = {}
390        for typ in supportedAccessSpecifier: r.update(self.get_pure_virtual_methods(typ))        # returns dict
391        return r
392
393
394    def get_method_names( self, type='public' ): return [ meth['name'] for meth in self['methods'][ type ] ]
395
396    def get_pure_virtual_methods( self, type='public' ):
397        r = {}
398        for meth in self['methods'][ type ]:
399            if meth['pure_virtual']: r[ meth['name'] ] = meth
400        return r
401
402    def __init__(self, nameStack, curTemplate):
403        self['nested_classes'] = []
404        self['parent'] = None
405        self['abstract'] = False
406        self._public_enums = {}
407        self._public_structs = {}
408        self._public_typedefs = {}
409        self._public_forward_declares = []
410        self['namespace'] = ""
411
412        debug_print( "Class:    %s"%nameStack )
413        debug_print( "Template: %s"%curTemplate)
414
415        if (len(nameStack) < 2):
416            nameStack.insert(1, "")#anonymous struct
417        global doxygenCommentCache
418        if len(doxygenCommentCache):
419            self["doxygen"] = doxygenCommentCache
420            doxygenCommentCache = ""
421
422        if "::" in "".join(nameStack):
423            #Re-Join class paths (ex  ['class', 'Bar', ':', ':', 'Foo'] -> ['class', 'Bar::Foo']
424            try:
425                new_nameStack = []
426                for name in nameStack:
427                    if len(new_nameStack) == 0:
428                        new_nameStack.append(name)
429                    elif name == ":" and new_nameStack[-1].endswith(":"):
430                        new_nameStack[-1] += name
431                    elif new_nameStack[-1].endswith("::"):
432                        new_nameStack[-2] += new_nameStack[-1] + name
433                        del new_nameStack[-1]
434                    else:
435                        new_nameStack.append(name)
436                trace_print("Convert from namestack\n %s\nto\n%s"%(nameStack, new_nameStack))
437                nameStack = new_nameStack
438            except: pass
439
440        # Handle final specifier
441        self["final"] = False
442        try:
443            final_index = nameStack.index("final")
444            # Dont trip up the rest of the logic
445            del nameStack[final_index]
446            self["final"] = True
447            trace_print("final")
448        except: pass
449
450        self["name"] = nameStack[1]
451        self["line_number"] = detect_lineno(nameStack[0])
452
453        #Handle template classes
454        if len(nameStack) > 3 and nameStack[2].startswith("<"):
455            open_template_count = 0
456            param_separator = 0
457            found_first = False
458            i = 0
459            for elm in nameStack:
460                if '<' in elm :
461                    open_template_count += 1
462                    found_first = True
463                elif '>' in elm:
464                    open_template_count -= 1
465                if found_first and open_template_count == 0:
466                    self["name"] = "".join(nameStack[1:i + 1])
467                    break;
468                i += 1
469        elif ":" in nameStack:
470            self['name'] = nameStack[ nameStack.index(':') - 1 ]
471
472        inheritList = []
473
474        if nameStack.count(':') == 1:
475            nameStack = nameStack[nameStack.index(":") + 1:]
476            while len(nameStack):
477                tmpStack = []
478                tmpInheritClass = {"access":"private", "virtual": False}
479                if "," in nameStack:
480                    tmpStack = nameStack[:nameStack.index(",")]
481                    nameStack = nameStack[nameStack.index(",") + 1:]
482                else:
483                    tmpStack = nameStack
484                    nameStack = []
485
486                # Convert template classes to one name in the last index
487                for i in range(0, len(tmpStack)):
488                    if '<' in tmpStack[i]:
489                        tmpStack2 = tmpStack[:i-1]
490                        tmpStack2.append("".join(tmpStack[i-1:]))
491                        tmpStack = tmpStack2
492                        break
493                if len(tmpStack) == 0:
494                    break;
495                elif len(tmpStack) == 1:
496                    tmpInheritClass["class"] = tmpStack[0]
497                elif len(tmpStack) == 2:
498                    tmpInheritClass["access"] = tmpStack[0]
499                    tmpInheritClass["class"] = tmpStack[1]
500                elif len(tmpStack) == 3 and "virtual" in tmpStack:
501                    tmpInheritClass["access"] = tmpStack[1] if tmpStack[1] != "virtual" else tmpStack[0]
502                    tmpInheritClass["class"] = tmpStack[2]
503                    tmpInheritClass["virtual"] = True
504                else:
505                    warning_print( "Warning: can not parse inheriting class %s"%(" ".join(tmpStack)))
506                    if '>' in tmpStack: pass    # allow skip templates for now
507                    else: raise NotImplemented
508
509                if 'class' in tmpInheritClass: inheritList.append(tmpInheritClass)
510
511        elif nameStack.count(':') == 2: self['parent'] = self['name']; self['name'] = nameStack[-1]
512
513        elif nameStack.count(':') > 2 and nameStack[0] in ("class", "struct"):
514            tmpStack = nameStack[nameStack.index(":") + 1:]
515
516            superTmpStack = [[]]
517            for tok in tmpStack:
518                if tok == ',':
519                    superTmpStack.append([])
520                else:
521                    superTmpStack[-1].append(tok)
522
523            for tmpStack in superTmpStack:
524                tmpInheritClass = {"access":"private"}
525
526                if len(tmpStack) and tmpStack[0] in supportedAccessSpecifier:
527                    tmpInheritClass["access"] = tmpStack[0]
528                    tmpStack = tmpStack[1:]
529
530                inheritNSStack = []
531                while len(tmpStack) > 3:
532                    if tmpStack[0] == ':': break;
533                    if tmpStack[1] != ':': break;
534                    if tmpStack[2] != ':': break;
535                    inheritNSStack.append(tmpStack[0])
536                    tmpStack = tmpStack[3:]
537                if len(tmpStack) == 1 and tmpStack[0] != ':':
538                     inheritNSStack.append(tmpStack[0])
539                tmpInheritClass["class"] = "::".join(inheritNSStack)
540                inheritList.append(tmpInheritClass)
541
542        self['inherits'] = inheritList
543
544        if curTemplate:
545            self["template"] = curTemplate
546            trace_print("Setting template to '%s'"%self["template"])
547
548        methodAccessSpecificList = {}
549        propertyAccessSpecificList = {}
550        enumAccessSpecificList = {}
551        structAccessSpecificList = {}
552        typedefAccessSpecificList = {}
553        forwardAccessSpecificList = {}
554
555        for accessSpecifier in supportedAccessSpecifier:
556            methodAccessSpecificList[accessSpecifier] = []
557            propertyAccessSpecificList[accessSpecifier] = []
558            enumAccessSpecificList[accessSpecifier] = []
559            structAccessSpecificList[accessSpecifier] = []
560            typedefAccessSpecificList[accessSpecifier] = []
561            forwardAccessSpecificList[accessSpecifier] = []
562
563        self['methods'] = methodAccessSpecificList
564        self['properties'] = propertyAccessSpecificList
565        self['enums'] = enumAccessSpecificList
566        self['structs'] = structAccessSpecificList
567        self['typedefs'] = typedefAccessSpecificList
568        self['forward_declares'] = forwardAccessSpecificList
569
570
571    def show(self):
572        """Convert class to a string"""
573        namespace_prefix = ""
574        if self["namespace"]: namespace_prefix = self["namespace"] + "::"
575        rtn = "%s %s"%(self["declaration_method"], namespace_prefix + self["name"])
576        if self["final"]: rtn += " final"
577        if self['abstract']: rtn += '    (abstract)\n'
578        else: rtn += '\n'
579
580        if 'doxygen' in list(self.keys()): rtn += self["doxygen"] + '\n'
581        if 'parent' in list(self.keys()) and self['parent']: rtn += 'parent class: ' + self['parent'] + '\n'
582
583        if "inherits" in list(self.keys()):
584            rtn += "  Inherits: "
585            for inheritClass in self["inherits"]:
586                if inheritClass["virtual"]: rtn += "virtual "
587                rtn += "%s %s, "%(inheritClass["access"], inheritClass["class"])
588            rtn += "\n"
589        rtn += "  {\n"
590        for accessSpecifier in supportedAccessSpecifier:
591            rtn += "    %s\n"%(accessSpecifier)
592            #Enums
593            if (len(self["enums"][accessSpecifier])):
594                rtn += "        <Enums>\n"
595            for enum in self["enums"][accessSpecifier]:
596                rtn += "            %s\n"%(repr(enum))
597            #Properties
598            if (len(self["properties"][accessSpecifier])):
599                rtn += "        <Properties>\n"
600            for property in self["properties"][accessSpecifier]:
601                rtn += "            %s\n"%(repr(property))
602            #Methods
603            if (len(self["methods"][accessSpecifier])):
604                rtn += "        <Methods>\n"
605            for method in self["methods"][accessSpecifier]:
606                rtn += "\t\t" + method.show() + '\n'
607        rtn += "  }\n"
608        print(rtn)
609
610    def __str__(self):
611        """Convert class to a string"""
612        namespace_prefix = ""
613        if self["namespace"]: namespace_prefix = self["namespace"] + "::"
614        rtn = "%s %s"%(self["declaration_method"], namespace_prefix + self["name"])
615        if self["final"]: rtn += " final"
616        if self['abstract']: rtn += '    (abstract)\n'
617        else: rtn += '\n'
618
619        if 'doxygen' in list(self.keys()): rtn += self["doxygen"] + '\n'
620        if 'parent' in list(self.keys()) and self['parent']: rtn += 'parent class: ' + self['parent'] + '\n'
621
622        if "inherits" in list(self.keys()) and len(self["inherits"]):
623            rtn += "Inherits: "
624            for inheritClass in self["inherits"]:
625                if inheritClass.get("virtual", False): rtn += "virtual "
626                rtn += "%s %s, "%(inheritClass["access"], inheritClass["class"])
627            rtn += "\n"
628        rtn += "{\n"
629        for accessSpecifier in supportedAccessSpecifier:
630            rtn += "%s\n"%(accessSpecifier)
631            #Enums
632            if (len(self["enums"][accessSpecifier])):
633                rtn += "    // Enums\n"
634            for enum in self["enums"][accessSpecifier]:
635                rtn += "    %s\n"%(repr(enum))
636            #Properties
637            if (len(self["properties"][accessSpecifier])):
638                rtn += "    // Properties\n"
639            for property in self["properties"][accessSpecifier]:
640                rtn += "    %s\n"%(repr(property))
641            #Methods
642            if (len(self["methods"][accessSpecifier])):
643                rtn += "    // Methods\n"
644            for method in self["methods"][accessSpecifier]:
645                rtn += "   %s\n"%(repr(method))
646        rtn += "}\n"
647        return rtn
648
649
650class CppUnion( CppClass ):
651    """Takes a name stack and turns it into a union
652
653    Contains the following Keys:
654    self['name'] - Name of the union
655    self['doxygen'] - Doxygen comments associated with the union if they exist
656    self['members'] - List of members the union has
657
658    An example of how this could look is as follows:
659    #self =
660    {
661        'name': ""
662        'members': []
663    }
664    """
665
666    def __init__(self, nameStack):
667        CppClass.__init__(self, nameStack, None)
668        self["name"] = "union " + self["name"]
669        self["members"] = self["properties"]["public"]
670
671    def transform_to_union_keys(self):
672        print("union keys: %s"%list(self.keys()))
673        for key in ['inherits', 'parent', 'abstract', 'namespace', 'typedefs', 'methods']:
674            del self[key]
675
676    def show(self):
677        """Convert class to a string"""
678        print(self)
679
680
681    def __str__(self):
682        """Convert class to a string"""
683        namespace_prefix = ""
684        if self["namespace"]: namespace_prefix = self["namespace"] + "::"
685        rtn = "%s %s"%(self["declaration_method"], namespace_prefix + self["name"])
686        if self['abstract']: rtn += '    (abstract)\n'
687        else: rtn += '\n'
688
689        if 'doxygen' in list(self.keys()): rtn += self["doxygen"] + '\n'
690        if 'parent' in list(self.keys()) and self['parent']: rtn += 'parent class: ' + self['parent'] + '\n'
691
692        rtn += "{\n"
693        for member in self["members"]:
694            rtn += "    %s\n"%(repr(member))
695        rtn += "}\n"
696        return rtn
697
698
699
700class _CppMethod( dict ):
701    def _params_helper1( self, stack ):
702        # deal with "throw" keyword
703        if 'throw' in stack: stack = stack[ : stack.index('throw') ]
704
705        ## remove GCC keyword __attribute__(...) and preserve returns ##
706        cleaned = []
707        hit = False; hitOpen = 0; hitClose = 0
708        for a in stack:
709            if a == '__attribute__': hit = True
710            if hit:
711                if a == '(': hitOpen += 1
712                elif a == ')': hitClose += 1
713                if a==')' and hitOpen == hitClose:
714                    hit = False
715            else:
716                cleaned.append( a )
717        stack = cleaned
718
719        # also deal with attribute((const)) function prefix #
720        # TODO this needs to be better #
721        if len(stack) > 5:
722            a = ''.join(stack)
723            if a.startswith('((__const__))'): stack = stack[ 5 : ]
724            elif a.startswith('__attribute__((__const__))'): stack = stack[ 6 : ]
725
726        stack = stack[stack.index('(') + 1: ]
727        if not stack: return []
728        if len(stack)>=3 and stack[0]==')' and stack[1]==':':    # is this always a constructor?
729            self['constructor'] = True
730            return []
731
732        stack.reverse(); _end_ = stack.index(')'); stack.reverse()
733        stack = stack[ : len(stack)-(_end_+1) ]
734        if '(' not in stack: return stack    # safe to return, no defaults that init a class
735
736        # transforms ['someclass', '(', '0', '0', '0', ')'] into "someclass(0,0,0)'"
737        r = []; hit=False
738        for a in stack:
739            if a == '(': hit=True
740            elif a == ')': hit=False
741            if hit or a == ')': r[-1] = r[-1] + a
742            else: r.append( a )
743        return r
744
745    def _params_helper2( self, params ):
746        for p in params:
747            p['method'] = self        # save reference in variable to parent method
748            if '::' in p['type']:
749                ns = p['type'].split('::')[0]
750                if ns not in Resolver.NAMESPACES and ns in Resolver.CLASSES:
751                    p['type'] = self['namespace'] + p['type']
752            else: p['namespace'] = self[ 'namespace' ]
753
754class CppMethod( _CppMethod ):
755    """Takes a name stack and turns it into a method
756
757    Contains the following Keys:
758    self['rtnType'] - Return type of the method (ex. "int")
759    self['name'] - Name of the method (ex. "getSize")
760    self['doxygen'] - Doxygen comments associated with the method if they exist
761    self['parameters'] - List of CppVariables
762    """
763    def show(self):
764        r = ['method name: %s (%s)' %(self['name'],self['debug']) ]
765        if self['returns']: r.append( 'returns: %s'%self['returns'] )
766        if self['parameters']: r.append( 'number arguments: %s' %len(self['parameters']))
767        if self['pure_virtual']: r.append( 'pure virtual: %s'%self['pure_virtual'] )
768        if self['constructor']: r.append( 'constructor' )
769        if self['destructor']: r.append( 'destructor' )
770        return '\n\t\t  '.join( r )
771
772    def __init__(self, nameStack, curClass, methinfo, curTemplate):
773        debug_print( "Method:   %s"%nameStack )
774        debug_print( "Template: %s"%curTemplate )
775        global doxygenCommentCache
776        if len(doxygenCommentCache):
777            self["doxygen"] = doxygenCommentCache
778            doxygenCommentCache = ""
779        if "operator" in nameStack:
780            self["rtnType"] = " ".join(nameStack[:nameStack.index('operator')])
781            self["name"] = "".join(nameStack[nameStack.index('operator'):nameStack.index('(')])
782        else:
783            self["rtnType"] = " ".join(nameStack[:nameStack.index('(') - 1])
784            self["name"] = " ".join(nameStack[nameStack.index('(') - 1:nameStack.index('(')])
785        if self["rtnType"].startswith("virtual"):
786           self["rtnType"] = self["rtnType"][len("virtual"):].strip()
787        if len(self["rtnType"]) == 0 or self["name"] == curClass:
788            self["rtnType"] = "void"
789
790        self["rtnType"] = self["rtnType"].replace(' : : ', '::' )
791        self["rtnType"] = self["rtnType"].replace(" <","<")
792        self["rtnType"] = self["rtnType"].replace(" >",">").replace(">>", "> >").replace(">>", "> >")
793        self["rtnType"] = self["rtnType"].replace(" ,",",")
794
795        for spec in ["const", "final", "override"]:
796            self[spec] = False
797            for i in reversed(nameStack):
798                if i == spec:
799                    self[spec] = True
800                    break
801                elif i == ")":
802                    break
803
804        self.update( methinfo )
805        self["line_number"] = detect_lineno(nameStack[0])
806
807        #Filter out initializer lists used in constructors
808        try:
809            paren_depth_counter = 0
810            for i in range(0, len(nameStack)):
811                elm = nameStack[i]
812                if elm == "(":
813                    paren_depth_counter += 1
814                if elm == ")":
815                    paren_depth_counter -=1
816                    if paren_depth_counter == 0 and nameStack[i+1] == ':':
817                        debug_print("Stripping out initializer list")
818                        nameStack = nameStack[:i+1]
819                        break
820        except: pass
821
822        paramsStack = self._params_helper1( nameStack )
823
824        debug_print( "curTemplate: %s"%curTemplate)
825        if curTemplate:
826            self["template"] = curTemplate
827            debug_print( "SET self['template'] to `%s`"%self["template"])
828
829        params = []
830        #See if there is a doxygen comment for the variable
831        doxyVarDesc = {}
832
833        if "doxygen" in self:
834            doxyLines = self["doxygen"].split("\n")
835            lastParamDesc = ""
836            for doxyLine in doxyLines:
837                if " @param " in doxyLine or " \param " in doxyLine:
838                    try:
839                        #Strip out the param
840                        doxyLine = doxyLine[doxyLine.find("param ") + 6:]
841                        (var, desc) = doxyLine.split(" ", 1)
842                        doxyVarDesc[var] = desc.strip()
843                        lastParamDesc = var
844                    except: pass
845                elif " @return " in doxyLine or " \return " in doxyLine:
846                    lastParamDesc = ""
847                    # not handled for now
848                elif lastParamDesc:
849                    try:
850                        doxyLine = doxyLine.strip()
851                        if " " not in doxyLine:
852                            lastParamDesc = ""
853                            continue
854                        doxyLine = doxyLine[doxyLine.find(" ") + 1:]
855                        doxyVarDesc[lastParamDesc] += " " + doxyLine
856                    except: pass
857
858        #Create the variable now
859        while (len(paramsStack)):
860            # Find commas that are not nexted in <>'s like template types
861            open_template_count = 0
862            param_separator = 0
863            i = 0
864            for elm in paramsStack:
865                if '<' in elm :
866                    open_template_count += 1
867                elif '>' in elm:
868                    open_template_count -= 1
869                elif elm == ',' and open_template_count == 0:
870                    param_separator = i
871                    break
872                i += 1
873
874            if param_separator:
875                param = CppVariable(paramsStack[0:param_separator],  doxyVarDesc=doxyVarDesc)
876                if len(list(param.keys())): params.append(param)
877                paramsStack = paramsStack[param_separator + 1:]
878            else:
879                param = CppVariable(paramsStack,  doxyVarDesc=doxyVarDesc)
880                if len(list(param.keys())): params.append(param)
881                break
882
883
884        self["parameters"] = params
885        self._params_helper2( params )    # mods params inplace
886
887    def __str__(self):
888        filter_keys = ("parent", "defined", "operator", "returns_reference")
889        cpy = dict((k,v) for (k,v) in list(self.items()) if k not in filter_keys)
890        return "%s"%cpy
891
892
893class _CppVariable(dict):
894    def _name_stack_helper( self, stack ):
895        stack = list(stack)
896        if '=' not in stack:        # TODO refactor me
897            # check for array[n] and deal with funny array syntax: "int myvar:99"
898            array = []
899            while stack and stack[-1].isdigit(): array.append( stack.pop() )
900            if array: array.reverse(); self['array'] = int(''.join(array))
901            if stack and stack[-1].endswith(':'): stack[-1] = stack[-1][:-1]
902
903        while stack and not stack[-1]: stack.pop()            # can be empty
904        return stack
905
906    def init(self):
907        #assert self['name']    # allow unnamed variables, methods like this: "void func(void);"
908        a = []
909        self['aliases'] = []; self['parent'] = None; self['typedef'] = None
910        for key in 'constant reference pointer static typedefs class fundamental unresolved'.split():
911            self[ key ] = 0
912        for b in self['type'].split():
913            if b == '__const__': b = 'const'
914            a.append( b )
915        self['type'] = ' '.join( a )
916
917
918class CppVariable( _CppVariable ):
919    """Takes a name stack and turns it into a method
920
921    Contains the following Keys:
922    self['type'] - Type for the variable (ex. "const string &")
923    self['name'] - Name of the variable (ex. "numItems")
924    self['namespace'] - Namespace containing the enum
925    self['desc'] - Description of the variable if part of a method (optional)
926    self['doxygen'] - Doxygen comments associated with the method if they exist
927    self['defaultValue'] - Default value of the variable, this key will only
928        exist if there is a default value
929    self['extern'] - True if its an extern, false if not
930    """
931    Vars = []
932    def __init__(self, nameStack,  **kwargs):
933        debug_print("trace %s"%nameStack)
934        if len(nameStack) and nameStack[0] == "extern":
935            self['extern'] = True
936            del nameStack[0]
937        else:
938            self['extern'] = False
939
940        _stack_ = nameStack
941        if "[" in nameStack: #strip off array informatin
942            arrayStack = nameStack[nameStack.index("["):]
943            if nameStack.count("[") > 1:
944                debug_print("Multi dimensional array")
945                debug_print("arrayStack=%s"%arrayStack)
946                nums = filter(lambda x: x.isdigit(), arrayStack)
947                # Calculate size by multiplying all dimensions
948                p = 1
949                for n in nums:
950                    p *= int(n)
951                #Multi dimensional array
952                self["array_size"] = p
953                self["multi_dimensional_array"] = 1
954                self["multi_dimensional_array_size"] = "x".join(nums)
955            else:
956                debug_print("Array")
957                if len(arrayStack) == 3:
958                    self["array_size"] = arrayStack[1]
959            nameStack = nameStack[:nameStack.index("[")]
960            self["array"] = 1
961        else:
962            self["array"] = 0
963        nameStack = self._name_stack_helper( nameStack )
964        global doxygenCommentCache
965        if len(doxygenCommentCache):
966            self["doxygen"] = doxygenCommentCache
967            doxygenCommentCache = ""
968
969        debug_print( "Variable: %s"%nameStack )
970
971        self["line_number"] = detect_lineno(nameStack[0])
972        self["function_pointer"] = 0
973
974        if (len(nameStack) < 2):    # +++
975            if len(nameStack) == 1: self['type'] = nameStack[0]; self['name'] = ''
976            else: error_print(_stack_); assert 0
977
978        elif is_function_pointer_stack(nameStack): #function pointer
979            self["type"] = " ".join(nameStack[:nameStack.index("(") + 2] + nameStack[nameStack.index(")")  :])
980            self["name"] = " ".join(nameStack[nameStack.index("(") + 2 : nameStack.index(")")])
981            self["function_pointer"] = 1
982
983        elif ("=" in nameStack):
984            self["type"] = " ".join(nameStack[:nameStack.index("=") - 1])
985            self["name"] = nameStack[nameStack.index("=") - 1]
986            self["defaultValue"] = " ".join(nameStack[nameStack.index("=") + 1:])    # deprecate camelCase in dicts
987            self['default'] = " ".join(nameStack[nameStack.index("=") + 1:])
988
989        elif is_fundamental(nameStack[-1]) or nameStack[-1] in ['>', '<' , ':', '.']:
990            #Un named parameter
991            self["type"] = " ".join(nameStack)
992            self["name"] = ""
993
994        else:    # common case
995            self["type"] = " ".join(nameStack[:-1])
996            self["name"] = nameStack[-1]
997
998        self["type"] = self["type"].replace(" :",":")
999        self["type"] = self["type"].replace(": ",":")
1000        self["type"] = self["type"].replace(" <","<")
1001        self["type"] = self["type"].replace(" >",">").replace(">>", "> >").replace(">>", "> >")
1002        self["type"] = self["type"].replace(" ,",",")
1003        #Optional doxygen description
1004        try:
1005            self["desc"] = kwargs["doxyVarDesc"][self["name"]]
1006        except: pass
1007
1008        self.init()
1009        CppVariable.Vars.append( self )        # save and resolve later
1010
1011    def __str__(self):
1012        keys_white_list = ['constant','name','reference','type','static','pointer','desc', 'line_number', 'extern']
1013        cpy = dict((k,v) for (k,v) in list(self.items()) if k in keys_white_list)
1014        if "array_size" in self: cpy["array_size"] = self["array_size"]
1015        return "%s"%cpy
1016
1017class _CppEnum(dict):
1018    def resolve_enum_values( self, values ):
1019        """Evaluates the values list of dictionaries passed in and figures out what the enum value
1020        for each enum is editing in place:
1021
1022        Example:
1023        From: [{'name': 'ORANGE'},
1024               {'name': 'RED'},
1025               {'name': 'GREEN', 'value': '8'}]
1026        To:   [{'name': 'ORANGE', 'value': 0},
1027               {'name': 'RED', 'value': 1},
1028               {'name': 'GREEN', 'value': 8}]
1029        """
1030        t = int; i = 0
1031        names = [ v['name'] for v in values ]
1032        for v in values:
1033            if 'value' in v:
1034                a = v['value'].strip()
1035                # Remove single quotes from single quoted chars (unless part of some expression
1036                if len(a) == 3 and a[0] == "'" and a[2] == "'":
1037                    a = v['value'] = a[1]
1038                if a.lower().startswith("0x"):
1039                    try:
1040                        i = a = int(a , 16)
1041                    except:pass
1042                elif a.isdigit():
1043                    i = a = int( a )
1044                elif a in names:
1045                    for other in values:
1046                        if other['name'] == a:
1047                            v['value'] = other['value']
1048                            break
1049
1050                elif '"' in a or "'" in a: t = str # only if there are quotes it this a string enum
1051                else:
1052                    try:
1053                        a = i = ord(a)
1054                    except: pass
1055                #Allow access of what is in the file pre-convert if converted
1056                if v['value'] != str(a):
1057                    v['raw_value'] = v['value']
1058                v['value'] = a
1059            else: v['value'] = i
1060            try:
1061                v['value'] = v['value'].replace(" < < ", " << ").replace(" >> ", " >> ")
1062            except: pass
1063            i += 1
1064        return t
1065
1066class CppEnum(_CppEnum):
1067    """Takes a name stack and turns it into an Enum
1068
1069    Contains the following Keys:
1070    self['name'] - Name of the enum (ex. "ItemState")
1071    self['namespace'] - Namespace containing the enum
1072    self['values'] - List of values where the values are a dictionary of the
1073        form {"name": name of the key (ex. "PARSING_HEADER"),
1074                  "value": Specified value of the enum, this key will only exist
1075                    if a value for a given enum value was defined
1076                }
1077    """
1078    def __init__(self, nameStack):
1079        global doxygenCommentCache
1080        if len(doxygenCommentCache):
1081            self["doxygen"] = doxygenCommentCache
1082            doxygenCommentCache = ""
1083        if len(nameStack) == 3 and nameStack[0] == "enum":
1084            debug_print("Created enum as just name/value")
1085            self["name"] = nameStack[1]
1086            self["instances"]=[nameStack[2]]
1087        if len(nameStack) < 4 or "{" not in nameStack or "}" not in nameStack:
1088            #Not enough stuff for an enum
1089            debug_print("Bad enum")
1090            return
1091        valueList = []
1092        self["line_number"] = detect_lineno(nameStack[0])
1093        #Figure out what values it has
1094        valueStack = nameStack[nameStack.index('{') + 1: nameStack.index('}')]
1095        while len(valueStack):
1096            tmpStack = []
1097            if "," in valueStack:
1098                tmpStack = valueStack[:valueStack.index(",")]
1099                valueStack = valueStack[valueStack.index(",") + 1:]
1100            else:
1101                tmpStack = valueStack
1102                valueStack = []
1103            d = {}
1104            if len(tmpStack) == 1: d["name"] = tmpStack[0]
1105            elif len(tmpStack) >= 3 and tmpStack[1] == "=":
1106                d["name"] = tmpStack[0]; d["value"] = " ".join(tmpStack[2:])
1107            elif len(tmpStack) == 2 and tmpStack[1] == "=":
1108                debug_print( "WARN-enum: parser missed value for %s"%tmpStack[0] )
1109                d["name"] = tmpStack[0]
1110
1111            if d: valueList.append( d )
1112
1113        if len(valueList):
1114            self['type'] = self.resolve_enum_values( valueList )    # returns int for standard enum
1115            self["values"] = valueList
1116        else:
1117            warning_print( 'WARN-enum: empty enum %s'%nameStack )
1118            return
1119        #Figure out if it has a name
1120        preBraceStack = nameStack[:nameStack.index("{")]
1121        postBraceStack = nameStack[nameStack.index("}") + 1:]
1122        self["typedef"] = False
1123        if (len(preBraceStack) == 4 and ":" in nameStack and "typedef" not in nameStack):
1124            # C++11 specify enum type with "enum <enum_name> : <type> ..." syntax
1125            self["name"] = preBraceStack[1]
1126            self["type"] = preBraceStack[3]
1127        elif (len(preBraceStack) == 2 and "typedef" not in nameStack):
1128            # enum "enum <enum_name> ..." syntax
1129            self["name"] = preBraceStack[1]
1130        elif len(postBraceStack) and "typedef" in nameStack:
1131            self["name"] = " ".join(postBraceStack)
1132            self["typedef"] = True
1133        else: warning_print( 'WARN-enum: nameless enum %s'%nameStack )
1134        #See if there are instances of this
1135        if "typedef" not in nameStack and len(postBraceStack):
1136            self["instances"] = []
1137            for var in postBraceStack:
1138                if "," in var:
1139                    continue
1140                self["instances"].append(var)
1141        self["namespace"] = ""
1142
1143
1144class CppStruct(dict):
1145    Structs = []
1146    def __init__(self, nameStack):
1147        if len(nameStack) >= 2: self['type'] = nameStack[1]
1148        else: self['type'] = None
1149        self['fields'] = []
1150        self.Structs.append( self )
1151        global curLine
1152        self["line_number"] = curLine
1153
1154C99_NONSTANDARD = {
1155    'int8' : 'signed char',
1156    'int16' : 'short int',
1157    'int32' : 'int',
1158    'int64' : 'int64_t',        # this can be: long int (64bit), or long long int (32bit)
1159    'uint' : 'unsigned int',
1160    'uint8' : 'unsigned char',
1161    'uint16' : 'unsigned short int',
1162    'uint32' : 'unsigned int',
1163    'uint64' : 'uint64_t',    # depends on host bits
1164}
1165
1166
1167def standardize_fundamental( s ):
1168    if s in C99_NONSTANDARD: return C99_NONSTANDARD[ s ]
1169    else: return s
1170
1171
1172class Resolver(object):
1173    C_FUNDAMENTAL = 'size_t unsigned signed bool char wchar short int float double long void'.split()
1174    C_FUNDAMENTAL += 'struct union enum'.split()
1175
1176
1177    SubTypedefs = {}        # TODO deprecate?
1178    NAMESPACES = []
1179    CLASSES = {}
1180    STRUCTS = {}
1181
1182    def initextra(self):
1183        self.typedefs = {}
1184        self.typedefs_order = []
1185        self.classes_order = []
1186        self.structs = Resolver.STRUCTS
1187        self.structs_order = []
1188        self.namespaces = Resolver.NAMESPACES        # save all namespaces
1189        self.curStruct = None
1190        self.stack = []    # full name stack, good idea to keep both stacks? (simple stack and full stack)
1191        self._classes_brace_level = {}    # class name : level
1192        self._structs_brace_level = {}        # struct type : level
1193        self._method_body = None
1194        self._forward_decls = []
1195        self._template_typenames = []    # template<typename XXX>
1196
1197    def current_namespace(self): return self.cur_namespace(True)
1198
1199    def cur_namespace(self, add_double_colon=False):
1200        rtn = ""
1201        i = 0
1202        while i < len(self.nameSpaces):
1203            rtn += self.nameSpaces[i]
1204            if add_double_colon or i < len(self.nameSpaces) - 1: rtn += "::"
1205            i+=1
1206        return rtn
1207
1208
1209    def guess_ctypes_type( self, string ):
1210        pointers = string.count('*')
1211        string = string.replace('*','')
1212
1213        a = string.split()
1214        if 'unsigned' in a: u = 'u'
1215        else: u = ''
1216        if 'long' in a and 'double' in a: b = 'longdouble'    # there is no ctypes.c_ulongdouble (this is a 64bit float?)
1217        elif a.count('long') == 2 and 'int' in a: b = '%sint64' %u
1218        elif a.count('long') == 2: b = '%slonglong' %u
1219        elif 'long' in a: b = '%slong' %u
1220        elif 'double' in a: b = 'double'    # no udouble in ctypes
1221        elif 'short' in a: b = '%sshort' %u
1222        elif 'char' in a: b = '%schar' %u
1223        elif 'wchar' in a: b = 'wchar'
1224        elif 'bool' in a: b = 'bool'
1225        elif 'float' in a: b = 'float'
1226
1227        elif 'int' in a: b = '%sint' %u
1228        elif 'int8' in a: b = 'int8'
1229        elif 'int16' in a: b = 'int16'
1230        elif 'int32' in a: b = 'int32'
1231        elif 'int64' in a: b = 'int64'
1232
1233        elif 'uint' in a: b = 'uint'
1234        elif 'uint8' in a: b = 'uint8'
1235        elif 'uint16' in a: b = 'uint16'
1236        elif 'uint32' in a: b = 'uint32'
1237        elif 'uint64' in a: b = 'uint64'
1238
1239        elif 'size_t' in a: b = 'size_t'
1240        elif 'void' in a: b = 'void_p'
1241
1242        elif string in 'struct union'.split(): b = 'void_p'    # what should be done here? don't trust struct, it could be a class, no need to expose via ctypes
1243        else: b = 'void_p'
1244
1245        if not pointers: return 'ctypes.c_%s' %b
1246        else:
1247            x = ''
1248            for i in range(pointers): x += 'ctypes.POINTER('
1249            x += 'ctypes.c_%s' %b
1250            x += ')' * pointers
1251            return x
1252
1253    def resolve_type( self, string, result ):    # recursive
1254        '''
1255        keeps track of useful things like: how many pointers, number of typedefs, is fundamental or a class, etc...
1256        '''
1257        ## be careful with templates, what is inside <something*> can be a pointer but the overall type is not a pointer
1258        ## these come before a template
1259        s = string.split('<')[0]
1260        result[ 'constant' ] += s.split().count('const')
1261        result[ 'static' ] += s.split().count('static')
1262        result[ 'mutable' ] = 'mutable' in s.split()
1263
1264        ## these come after a template
1265        s = string.split('>')[-1]
1266        result[ 'pointer' ] += s.count('*')
1267        result[ 'reference' ] += s.count('&')
1268
1269
1270        x = string; alias = False
1271        for a in '* & const static mutable'.split(): x = x.replace(a,'')
1272        for y in x.split():
1273            if y not in self.C_FUNDAMENTAL: alias = y; break
1274
1275        #if alias == 'class':
1276        #    result['class'] = result['name']    # forward decl of class
1277        #    result['forward_decl'] = True
1278        if alias == '__extension__': result['fundamental_extension'] = True
1279        elif alias:
1280            result['aliases'].append( alias )
1281            if alias in C99_NONSTANDARD:
1282                result['type'] = C99_NONSTANDARD[ alias ]
1283                result['typedef'] = alias
1284                result['typedefs'] += 1
1285            elif alias in self.typedefs:
1286                result['typedefs'] += 1
1287                result['typedef'] = alias
1288                self.resolve_type( self.typedefs[alias], result )
1289            elif alias in self.classes:
1290                klass = self.classes[alias]; result['fundamental'] = False
1291                result['class'] = klass
1292                result['unresolved'] = False
1293            else: result['unresolved'] = True
1294        else:
1295            result['fundamental'] = True
1296            result['unresolved'] = False
1297
1298
1299    def finalize_vars(self):
1300        for s in CppStruct.Structs:    # vars within structs can be ignored if they do not resolve
1301            for var in s['fields']: var['parent'] = s['type']
1302        #for c in self.classes.values():
1303        #    for var in c.get_all_properties(): var['parent'] = c['name']
1304
1305        ## RESOLVE ##
1306        for var in CppVariable.Vars:
1307            self.resolve_type( var['type'], var )
1308            #if 'method' in var and var['method']['name'] ==  '_notifyCurrentCamera': print(var); assert 0
1309
1310        # then find concrete type and best guess ctypes type #
1311        for var in CppVariable.Vars:
1312            if not var['aliases']:    #var['fundamental']:
1313                var['ctypes_type'] = self.guess_ctypes_type( var['type'] )
1314            else:
1315                var['unresolved'] = False    # below may test to True
1316                if var['class']:
1317                    var['ctypes_type'] = 'ctypes.c_void_p'
1318                else:
1319                    assert var['aliases']
1320                    tag = var['aliases'][0]
1321
1322                    klass = None
1323                    nestedEnum = None
1324                    nestedStruct = None
1325                    nestedTypedef = None
1326                    if 'method' in var and 'parent' in list(var['method'].keys()):
1327                        klass = var['method']['parent']
1328                        if tag in var['method']['parent']._public_enums:
1329                            nestedEnum = var['method']['parent']._public_enums[ tag ]
1330                        elif tag in var['method']['parent']._public_structs:
1331                            nestedStruct = var['method']['parent']._public_structs[ tag ]
1332                        elif tag in var['method']['parent']._public_typedefs:
1333                            nestedTypedef = var['method']['parent']._public_typedefs[ tag ]
1334
1335
1336                    if '<' in tag:    # should also contain '>'
1337                        var['template'] = tag        # do not resolve templates
1338                        var['ctypes_type'] = 'ctypes.c_void_p'
1339                        var['unresolved'] = True
1340
1341                    elif nestedEnum:
1342                        enum = nestedEnum
1343                        if enum['type'] is int:
1344                            var['ctypes_type'] = 'ctypes.c_int'
1345                            var['raw_type'] = 'int'
1346
1347                        elif enum['type'] is str:
1348                            var['ctypes_type'] = 'ctypes.c_char_p'
1349                            var['raw_type'] = 'char*'
1350
1351                        var['enum'] = var['method']['path'] + '::' + enum['name']
1352                        var['fundamental'] = True
1353
1354                    elif nestedStruct:
1355                        var['ctypes_type'] = 'ctypes.c_void_p'
1356                        var['raw_type'] = var['method']['path'] + '::' + nestedStruct['type']
1357                        var['fundamental'] = False
1358
1359                    elif nestedTypedef:
1360                        var['fundamental'] = is_fundamental( nestedTypedef )
1361                        if not var['fundamental']:
1362                            var['raw_type'] = var['method']['path'] + '::' + tag
1363
1364                    else:
1365                        _tag = tag
1366                        if '::' in tag and tag.split('::')[0] in self.namespaces: tag = tag.split('::')[-1]
1367                        con = self.concrete_typedef( _tag )
1368                        if con:
1369                            var['concrete_type'] = con
1370                            var['ctypes_type'] = self.guess_ctypes_type( var['concrete_type'] )
1371
1372                        elif tag in self.structs:
1373                            trace_print( 'STRUCT', var )
1374                            var['struct'] = tag
1375                            var['ctypes_type'] = 'ctypes.c_void_p'
1376                            var['raw_type'] = self.structs[tag]['namespace'] + '::' + tag
1377
1378                        elif tag in self._forward_decls:
1379                            var['forward_declared'] = tag
1380                            var['ctypes_type'] = 'ctypes.c_void_p'
1381
1382                        elif tag in self.global_enums:
1383                            enum = self.global_enums[ tag ]
1384                            if enum['type'] is int:
1385                                var['ctypes_type'] = 'ctypes.c_int'
1386                                var['raw_type'] = 'int'
1387                            elif enum['type'] is str:
1388                                var['ctypes_type'] = 'ctypes.c_char_p'
1389                                var['raw_type'] = 'char*'
1390                            var['enum'] = enum['namespace'] + enum['name']
1391                            var['fundamental'] = True
1392
1393
1394                        elif var['parent']:
1395                            warning_print( 'WARN unresolved %s'%_tag)
1396                            var['ctypes_type'] = 'ctypes.c_void_p'
1397                            var['unresolved'] = True
1398
1399
1400                        elif tag.count('::')==1:
1401                            trace_print( 'trying to find nested something in', tag )
1402                            a = tag.split('::')[0]
1403                            b = tag.split('::')[-1]
1404                            if a in self.classes:    # a::b is most likely something nested in a class
1405                                klass = self.classes[ a ]
1406                                if b in klass._public_enums:
1407                                    trace_print( '...found nested enum', b )
1408                                    enum = klass._public_enums[ b ]
1409                                    if enum['type'] is int:
1410                                        var['ctypes_type'] = 'ctypes.c_int'
1411                                        var['raw_type'] = 'int'
1412                                    elif enum['type'] is str:
1413                                        var['ctypes_type'] = 'ctypes.c_char_p'
1414                                        var['raw_type'] = 'char*'
1415                                    try:
1416                                        if 'method' in var: var['enum'] = var['method']['path'] + '::' + enum['name']
1417                                        else:    # class property
1418                                            var['unresolved'] = True
1419                                    except:
1420                                        var['unresolved'] = True
1421
1422                                    var['fundamental'] = True
1423
1424                                else: var['unresolved'] = True    # TODO klass._public_xxx
1425
1426                            elif a in self.namespaces:    # a::b can also be a nested namespace
1427                                if b in self.global_enums:
1428                                    enum = self.global_enums[ b ]
1429                                    trace_print(enum)
1430                                trace_print(var)
1431                                assert 0
1432
1433                            elif b in self.global_enums:        # falling back, this is a big ugly
1434                                enum = self.global_enums[ b ]
1435                                assert a in enum['namespace'].split('::')
1436                                if enum['type'] is int:
1437                                    var['ctypes_type'] = 'ctypes.c_int'
1438                                    var['raw_type'] = 'int'
1439                                elif enum['type'] is str:
1440                                    var['ctypes_type'] = 'ctypes.c_char_p'
1441                                    var['raw_type'] = 'char*'
1442                                var['fundamental'] = True
1443
1444                            else:    # boost::gets::crazy
1445                                trace_print('NAMESPACES', self.namespaces)
1446                                trace_print( a, b )
1447                                trace_print( '---- boost gets crazy ----' )
1448                                var['ctypes_type'] = 'ctypes.c_void_p'
1449                                var['unresolved'] = True
1450
1451
1452                        elif 'namespace' in var and self.concrete_typedef(var['namespace']+tag):
1453                            #print( 'TRYING WITH NS', var['namespace'] )
1454                            con = self.concrete_typedef( var['namespace']+tag )
1455                            if con:
1456                                var['typedef'] = var['namespace']+tag
1457                                var['type'] = con
1458                                if 'struct' in con.split():
1459                                    var['raw_type'] = var['typedef']
1460                                    var['ctypes_type'] = 'ctypes.c_void_p'
1461                                else:
1462                                    self.resolve_type( var['type'], var )
1463                                    var['ctypes_type'] = self.guess_ctypes_type( var['type'] )
1464
1465                        elif '::' in var:
1466                            var['ctypes_type'] = 'ctypes.c_void_p'
1467                            var['unresolved'] = True
1468
1469                        elif tag in self.SubTypedefs:    # TODO remove SubTypedefs
1470                            if 'property_of_class' in var or 'property_of_struct' in var:
1471                                trace_print( 'class:', self.SubTypedefs[ tag ], 'tag:', tag )
1472                                var['typedef'] = self.SubTypedefs[ tag ]    # class name
1473                                var['ctypes_type'] = 'ctypes.c_void_p'
1474                            else:
1475                                trace_print( "WARN-this should almost never happen!" )
1476                                trace_print( var ); trace_print('-'*80)
1477                                var['unresolved'] = True
1478
1479                        elif tag in self._template_typenames:
1480                            var['typename'] = tag
1481                            var['ctypes_type'] = 'ctypes.c_void_p'
1482                            var['unresolved'] = True    # TODO, how to deal with templates?
1483
1484                        elif tag.startswith('_'):    # assume starting with underscore is not important for wrapping
1485                            warning_print( 'WARN unresolved %s'%_tag)
1486                            var['ctypes_type'] = 'ctypes.c_void_p'
1487                            var['unresolved'] = True
1488
1489                        else:
1490                            trace_print( 'WARN: unknown type', var )
1491                            assert 'property_of_class' in var or 'property_of_struct'    # only allow this case
1492                            var['unresolved'] = True
1493
1494
1495                    ## if not resolved and is a method param, not going to wrap these methods  ##
1496                    if var['unresolved'] and 'method' in var: var['method']['unresolved_parameters'] = True
1497
1498
1499        # create stripped raw_type #
1500        p = '* & const static mutable'.split()        # +++ new July7: "mutable"
1501        for var in CppVariable.Vars:
1502            if 'raw_type' not in var:
1503                raw = []
1504                for x in var['type'].split():
1505                    if x not in p: raw.append( x )
1506                var['raw_type'] = ' '.join( raw )
1507
1508                #if 'AutoConstantEntry' in var['raw_type']: print(var); assert 0
1509                if var['class']:
1510                    if '::' not in var['raw_type']:
1511                        if not var['class']['parent']:
1512                            var['raw_type'] = var['class']['namespace'] + '::' + var['raw_type']
1513                        elif var['class']['parent'] in self.classes:
1514                            parent = self.classes[ var['class']['parent'] ]
1515                            var['raw_type'] = parent['namespace'] + '::' + var['class']['name'] + '::' + var['raw_type']
1516                        else:
1517                            var['unresolved'] = True
1518
1519                    elif '::' in var['raw_type'] and var['raw_type'].split('::')[0] not in self.namespaces:
1520                        var['raw_type'] = var['class']['namespace'] + '::' + var['raw_type']
1521                    else:
1522                        var['unresolved'] = True
1523
1524                elif 'forward_declared' in var and 'namespace' in var:
1525                    if '::' not in var['raw_type']:
1526                        var['raw_type'] = var['namespace'] + var['raw_type']
1527                    elif '::' in var['raw_type'] and var['raw_type'].split('::')[0] in self.namespaces:
1528                        pass
1529                    else: trace_print('-'*80); trace_print(var); raise NotImplemented
1530
1531
1532            ## need full name space for classes in raw type ##
1533            if var['raw_type'].startswith( '::' ):
1534                #print(var)
1535                #print('NAMESPACE', var['class']['namespace'])
1536                #print( 'PARENT NS', var['class']['parent']['namespace'] )
1537                #assert 0
1538                var['unresolved'] = True
1539                if 'method' in var: var['method']['unresolved_parameters'] = True
1540                #var['raw_type'] = var['raw_type'][2:]
1541
1542        # Take care of #defines and #pragmas etc
1543        trace_print("Processing precomp_macro_buf: %s"%self._precomp_macro_buf)
1544        for m in self._precomp_macro_buf:
1545            macro = m.replace("<CppHeaderParser_newline_temp_replacement>\\n", "\n")
1546            try:
1547                if macro.lower().startswith("#define"):
1548                    trace_print("Adding #define %s"%macro)
1549                    self.defines.append(re.split("[\t ]+", macro, 1)[1].strip())
1550                elif macro.lower().startswith("#pragma"):
1551                    trace_print("Adding #pragma %s"%macro)
1552                    self.pragmas.append(re.split("[\t ]+", macro, 1)[1].strip())
1553                elif macro.lower().startswith("#include"):
1554                    trace_print("Adding #include %s"%macro)
1555                    self.includes.append(re.split("[\t ]+", macro, 1)[1].strip())
1556                else:
1557                    debug_print("Cant detect what to do with precomp macro '%s'"%macro)
1558            except: pass
1559        self._precomp_macro_buf = None
1560
1561
1562    def concrete_typedef( self, key ):
1563        if key not in self.typedefs:
1564            #print( 'FAILED typedef', key )
1565            return None
1566        while key in self.typedefs:
1567            prev = key
1568            key = self.typedefs[ key ]
1569            if '<' in key or '>' in key: return prev        # stop at template
1570            if key.startswith('std::'): return key        # stop at std lib
1571        return key
1572
1573
1574class _CppHeader( Resolver ):
1575    def finalize(self):
1576        self.finalize_vars()
1577        # finalize classes and method returns types
1578        for cls in list(self.classes.values()):
1579            for meth in cls.get_all_methods():
1580                if meth['pure_virtual']: cls['abstract'] = True
1581
1582                if not meth['returns_fundamental'] and meth['returns'] in C99_NONSTANDARD:
1583                    meth['returns'] = C99_NONSTANDARD[meth['returns']]
1584                    meth['returns_fundamental'] = True
1585
1586                elif not meth['returns_fundamental']:    # describe the return type
1587                    con = None
1588                    if cls['namespace'] and '::' not in meth['returns']:
1589                        con = self.concrete_typedef( cls['namespace'] + '::' + meth['returns'] )
1590                    else: con = self.concrete_typedef( meth['returns'] )
1591
1592
1593                    if con:
1594                        meth['returns_concrete'] = con
1595                        meth['returns_fundamental'] = is_fundamental( con )
1596
1597                    elif meth['returns'] in self.classes:
1598                        trace_print( 'meth returns class:', meth['returns'] )
1599                        meth['returns_class'] = True
1600
1601                    elif meth['returns'] in self.SubTypedefs:
1602                        meth['returns_class'] = True
1603                        meth['returns_nested'] = self.SubTypedefs[ meth['returns'] ]
1604
1605                    elif meth['returns'] in cls._public_enums:
1606                        enum = cls._public_enums[ meth['returns'] ]
1607                        meth['returns_enum'] = enum['type']
1608                        meth['returns_fundamental'] = True
1609                        if enum['type'] == int: meth['returns'] = 'int'
1610                        else: meth['returns'] = 'char*'
1611
1612                    elif meth['returns'] in self.global_enums:
1613                        enum = self.global_enums[ meth['returns'] ]
1614                        meth['returns_enum'] = enum['type']
1615                        meth['returns_fundamental'] = True
1616                        if enum['type'] == int: meth['returns'] = 'int'
1617                        else: meth['returns'] = 'char*'
1618
1619                    elif meth['returns'].count('::')==1:
1620                        trace_print( meth )
1621                        a,b = meth['returns'].split('::')
1622                        if a in self.namespaces:
1623                            if b in self.classes:
1624                                klass = self.classes[ b ]
1625                                meth['returns_class'] = a + '::' + b
1626                            elif '<' in b and '>' in b:
1627                                warning_print( 'WARN-can not return template: %s'%b )
1628                                meth['returns_unknown'] = True
1629                            elif b in self.global_enums:
1630                                enum = self.global_enums[ b ]
1631                                meth['returns_enum'] = enum['type']
1632                                meth['returns_fundamental'] = True
1633                                if enum['type'] == int: meth['returns'] = 'int'
1634                                else: meth['returns'] = 'char*'
1635
1636                            else: trace_print( a, b); trace_print( meth); meth['returns_unknown'] = True    # +++
1637
1638                        elif a in self.classes:
1639                            klass = self.classes[ a ]
1640                            if b in klass._public_enums:
1641                                trace_print( '...found nested enum', b )
1642                                enum = klass._public_enums[ b ]
1643                                meth['returns_enum'] = enum['type']
1644                                meth['returns_fundamental'] = True
1645                                if enum['type'] == int: meth['returns'] = 'int'
1646                                else: meth['returns'] = 'char*'
1647
1648                            elif b in klass._public_forward_declares:
1649                                meth['returns_class'] = True
1650
1651                            elif b in klass._public_typedefs:
1652                                typedef = klass._public_typedefs[ b ]
1653                                meth['returns_fundamental'] = is_fundamental( typedef )
1654
1655                            else:
1656                                trace_print( meth )    # should be a nested class, TODO fix me.
1657                                meth['returns_unknown'] = True
1658
1659                    elif '::' in meth['returns']:
1660                        trace_print('TODO namespace or extra nested return:', meth)
1661                        meth['returns_unknown'] = True
1662                    else:
1663                        trace_print( 'WARN: UNKNOWN RETURN', meth['name'], meth['returns'])
1664                        meth['returns_unknown'] = True
1665
1666                if meth["returns"].startswith(": : "):
1667                    meth["returns"] = meth["returns"].replace(": : ", "::")
1668
1669        for cls in list(self.classes.values()):
1670            methnames = cls.get_all_method_names()
1671            pvm = cls.get_all_pure_virtual_methods()
1672
1673            for d in cls['inherits']:
1674                c = d['class']
1675                a = d['access']    # do not depend on this to be 'public'
1676                trace_print( 'PARENT CLASS:', c )
1677                if c not in self.classes: trace_print('WARN: parent class not found')
1678                if c in self.classes and self.classes[c]['abstract']:
1679                    p = self.classes[ c ]
1680                    for meth in p.get_all_methods():    #p["methods"]["public"]:
1681                        trace_print( '\t\tmeth', meth['name'], 'pure virtual', meth['pure_virtual'] )
1682                        if meth['pure_virtual'] and meth['name'] not in methnames: cls['abstract'] = True; break
1683
1684
1685
1686
1687
1688    def evaluate_struct_stack(self):
1689        """Create a Struct out of the name stack (but not its parts)"""
1690        #print( 'eval struct stack', self.nameStack )
1691        #if self.braceDepth != len(self.nameSpaces): return
1692        struct = CppStruct(self.nameStack)
1693        struct["namespace"] = self.cur_namespace()
1694        self.structs[ struct['type'] ] = struct
1695        self.structs_order.append( struct )
1696        if self.curClass:
1697            struct['parent'] = self.curClass
1698            klass = self.classes[ self.curClass ]
1699            klass['structs'][self.curAccessSpecifier].append( struct )
1700            if self.curAccessSpecifier == 'public': klass._public_structs[ struct['type'] ] = struct
1701        self.curStruct = struct
1702        self._structs_brace_level[ struct['type'] ] = self.braceDepth
1703
1704
1705    def parse_method_type( self, stack ):
1706        trace_print( 'meth type info', stack )
1707        if stack[0] in ':;' and stack[1] != ':': stack = stack[1:]
1708        info = {
1709            'debug': ' '.join(stack).replace(' : : ', '::' ).replace(' < ', '<' ).replace(' > ', '> ' ).replace(" >",">").replace(">>", "> >").replace(">>", "> >"),
1710            'class':None,
1711            'namespace':self.cur_namespace(add_double_colon=True),
1712        }
1713
1714        for tag in 'defined pure_virtual operator constructor destructor extern template virtual static explicit inline friend returns returns_pointer returns_fundamental returns_class'.split(): info[tag]=False
1715        header = stack[ : stack.index('(') ]
1716        header = ' '.join( header )
1717        header = header.replace(' : : ', '::' )
1718        header = header.replace(' < ', '<' )
1719        header = header.replace(' > ', '> ' )
1720        header = header.strip()
1721
1722        if '{' in stack:
1723            info['defined'] = True
1724            self._method_body = self.braceDepth + 1
1725            trace_print( 'NEW METHOD WITH BODY', self.braceDepth )
1726        elif stack[-1] == ';':
1727            info['defined'] = False
1728            self._method_body = None    # not a great idea to be clearing here
1729        else: assert 0
1730
1731        if len(stack) > 3 and stack[-1] == ';' and stack[-2] == '0' and stack[-3] == '=':
1732            info['pure_virtual'] = True
1733
1734        r = header.split()
1735        name = None
1736        if 'operator' in stack:    # rare case op overload defined outside of class
1737            op = stack[ stack.index('operator')+1 : stack.index('(') ]
1738            op = ''.join(op)
1739            if not op:
1740                if " ".join(['operator', '(', ')', '(']) in " ".join(stack):
1741                    op = "()"
1742                else:
1743                    trace_print( 'Error parsing operator')
1744                    return None
1745
1746            info['operator'] = op
1747            name = 'operator' + op
1748            a = stack[ : stack.index('operator') ]
1749
1750        elif r:
1751            name = r[-1]
1752            a = r[ : -1 ]    # strip name
1753
1754        if name is None: return None
1755        #if name.startswith('~'): name = name[1:]
1756
1757        while a and a[0] == '}':    # strip - can have multiple } }
1758            a = a[1:]
1759
1760
1761        if '::' in name:
1762            #klass,name = name.split('::')    # methods can be defined outside of class
1763            klass = name[ : name.rindex('::') ]
1764            name = name.split('::')[-1]
1765            info['class'] = klass
1766            if klass in self.classes and not self.curClass:
1767                 #Class function defined outside the class
1768                return None
1769        #    info['name'] = name
1770        #else: info['name'] = name
1771
1772        if name.startswith('~'):
1773            info['destructor'] = True
1774            name = name[1:]
1775        elif not a or (name == self.curClass and len(self.curClass)):
1776            info['constructor'] = True
1777
1778        info['name'] = name
1779
1780        for tag in 'extern virtual static explicit inline friend'.split():
1781            if tag in a: info[ tag ] = True; a.remove( tag )    # inplace
1782        if 'template' in a:
1783            a.remove('template')
1784            b = ' '.join( a )
1785            if '>' in b:
1786                info['template'] = b[ : b.index('>')+1 ]
1787                info['returns'] = b[ b.index('>')+1 : ]    # find return type, could be incorrect... TODO
1788                if '<typename' in info['template'].split():
1789                    typname = info['template'].split()[-1]
1790                    typname = typname[ : -1 ]    # strip '>'
1791                    if typname not in self._template_typenames: self._template_typenames.append( typname )
1792            else: info['returns'] = ' '.join( a )
1793        else: info['returns'] = ' '.join( a )
1794        info['returns'] = info['returns'].replace(' <', '<').strip()
1795
1796        ## be careful with templates, do not count pointers inside template
1797        info['returns_pointer'] = info['returns'].split('>')[-1].count('*')
1798        if info['returns_pointer']: info['returns'] = info['returns'].replace('*','').strip()
1799
1800        info['returns_reference'] = '&' in info['returns']
1801        if info['returns']: info['returns'] = info['returns'].replace('&','').strip()
1802
1803        a = []
1804        for b in info['returns'].split():
1805            if b == '__const__': info['returns_const'] = True
1806            elif b == 'const': info['returns_const'] = True
1807            else: a.append( b )
1808        info['returns'] = ' '.join( a )
1809
1810        info['returns_fundamental'] = is_fundamental( info['returns'] )
1811        return info
1812
1813    def evaluate_method_stack(self):
1814        """Create a method out of the name stack"""
1815
1816        if self.curStruct:
1817            trace_print( 'WARN - struct contains methods - skipping' )
1818            trace_print( self.stack )
1819            assert 0
1820
1821        info = self.parse_method_type( self.stack )
1822        if info:
1823            if info[ 'class' ] and info['class'] in self.classes:     # case where methods are defined outside of class
1824                newMethod = CppMethod(self.nameStack, info['name'], info, self.curTemplate)
1825                klass = self.classes[ info['class'] ]
1826                klass[ 'methods' ][ 'public' ].append( newMethod )
1827                newMethod['parent'] = klass
1828                if klass['namespace']: newMethod['path'] = klass['namespace'] + '::' + klass['name']
1829                else: newMethod['path'] = klass['name']
1830
1831            elif self.curClass:    # normal case
1832                newMethod = CppMethod(self.nameStack, self.curClass, info, self.curTemplate)
1833                klass = self.classes[self.curClass]
1834                klass['methods'][self.curAccessSpecifier].append(newMethod)
1835                newMethod['parent'] = klass
1836                if klass['namespace']: newMethod['path'] = klass['namespace'] + '::' + klass['name']
1837                else: newMethod['path'] = klass['name']
1838            else: #non class functions
1839                debug_print("FREE FUNCTION")
1840                newMethod = CppMethod(self.nameStack, None, info, self.curTemplate)
1841                self.functions.append(newMethod)
1842            global parseHistory
1843            parseHistory.append({"braceDepth": self.braceDepth, "item_type": "method", "item": newMethod})
1844        else:
1845            trace_print( 'free function?', self.nameStack )
1846
1847        self.stack = []
1848
1849    def _parse_typedef( self, stack, namespace='' ):
1850        if not stack or 'typedef' not in stack: return
1851        stack = list( stack )    # copy just to be safe
1852        if stack[-1] == ';': stack.pop()
1853
1854        while stack and stack[-1].isdigit(): stack.pop()    # throw away array size for now
1855
1856        idx = stack.index('typedef')
1857        if stack[-1] == "]":
1858            try:
1859                name = namespace + "".join(stack[-4:])
1860                # Strip off the array part so the rest of the parsing is better
1861                stack = stack[:-3]
1862            except:
1863                name = namespace + stack[-1]
1864        else:
1865            name = namespace + stack[-1]
1866        s = ''
1867        for a in stack[idx+1:-1]:
1868            if a == '{': break
1869            if not s or s[-1] in ':<>' or a in ':<>': s += a    # keep compact
1870            else: s += ' ' + a    # spacing
1871
1872        r = {'name':name, 'raw':s, 'type':s}
1873        if not is_fundamental(s):
1874            if 'struct' in s.split(): pass        # TODO is this right? "struct ns::something"
1875            elif '::' not in s: s = namespace + s         # only add the current name space if no namespace given
1876            r['type'] = s
1877        if s: return r
1878
1879
1880    def evaluate_typedef(self):
1881        ns = self.cur_namespace(add_double_colon=True)
1882        res = self._parse_typedef( self.stack, ns )
1883        if res:
1884            name = res['name']
1885            self.typedefs[ name ] = res['type']
1886            if name not in self.typedefs_order: self.typedefs_order.append( name )
1887
1888
1889    def evaluate_property_stack(self):
1890        """Create a Property out of the name stack"""
1891        global parseHistory
1892        assert self.stack[-1] == ';'
1893        debug_print( "trace" )
1894        if self.nameStack[0] == 'typedef':
1895            if self.curClass:
1896                typedef = self._parse_typedef( self.stack )
1897                name = typedef['name']
1898                klass = self.classes[ self.curClass ]
1899                klass[ 'typedefs' ][ self.curAccessSpecifier ].append( name )
1900                if self.curAccessSpecifier == 'public': klass._public_typedefs[ name ] = typedef['type']
1901                Resolver.SubTypedefs[ name ] = self.curClass
1902            else: assert 0
1903        elif self.curStruct or self.curClass:
1904            if len(self.nameStack) == 1:
1905                #See if we can de anonymize the type
1906                filteredParseHistory = [h for h in parseHistory if h["braceDepth"] == self.braceDepth]
1907                if len(filteredParseHistory) and filteredParseHistory[-1]["item_type"] == "class":
1908                    self.nameStack.insert(0, filteredParseHistory[-1]["item"]["name"])
1909                    debug_print("DEANONYMOIZING %s to type '%s'"%(self.nameStack[1], self.nameStack[0]))
1910            if "," in self.nameStack: #Maybe we have a variable list
1911                #Figure out what part is the variable separator but remember templates of function pointer
1912                #First find left most comma outside of a > and )
1913                leftMostComma = 0;
1914                for i in range(0, len(self.nameStack)):
1915                    name = self.nameStack[i]
1916                    if name in (">", ")"): leftMostComma = 0
1917                    if leftMostComma == 0 and name == ",": leftMostComma = i
1918                # Is it really a list of variables?
1919                if leftMostComma != 0:
1920                    trace_print("Multiple variables for namestack in %s.  Separating processing"%self.nameStack)
1921                    orig_nameStack = self.nameStack[:]
1922                    orig_stack = self.stack[:]
1923
1924                    type_nameStack = orig_nameStack[:leftMostComma-1]
1925                    for name in orig_nameStack[leftMostComma - 1::2]:
1926                        self.nameStack = type_nameStack + [name]
1927                        self.stack = orig_stack[:] # Not maintained for mucking, but this path it doesnt matter
1928                        self.evaluate_property_stack()
1929                    return
1930
1931            newVar = CppVariable(self.nameStack)
1932            newVar['namespace'] = self.current_namespace()
1933            if self.curStruct:
1934                self.curStruct[ 'fields' ].append( newVar )
1935                newVar['property_of_struct'] = self.curStruct
1936            elif self.curClass:
1937                klass = self.classes[self.curClass]
1938                klass["properties"][self.curAccessSpecifier].append(newVar)
1939                newVar['property_of_class'] = klass['name']
1940            parseHistory.append({"braceDepth": self.braceDepth, "item_type": "variable", "item": newVar})
1941        else:
1942            debug_print( "Found Global variable" )
1943            newVar = CppVariable(self.nameStack)
1944            self.variables.append(newVar)
1945
1946        self.stack = []        # CLEAR STACK
1947
1948    def evaluate_class_stack(self):
1949        """Create a Class out of the name stack (but not its parts)"""
1950        #dont support sub classes today
1951        #print( 'eval class stack', self.nameStack )
1952        parent = self.curClass
1953        if self.braceDepth > len( self.nameSpaces) and parent:
1954            trace_print( 'HIT NESTED SUBCLASS' )
1955            self.accessSpecifierStack.append(self.curAccessSpecifier)
1956        elif self.braceDepth != len(self.nameSpaces):
1957            error_print( 'ERROR: WRONG BRACE DEPTH' )
1958            return
1959
1960        # When dealing with typedefed structs, get rid of typedef keyword to handle later on
1961        if self.nameStack[0] == "typedef":
1962            del self.nameStack[0]
1963            if len(self.nameStack) == 1:
1964                self.anon_struct_counter += 1
1965                # We cant handle more than 1 anonymous struct, so name them uniquely
1966                self.nameStack.append("<anon-struct-%d>"%self.anon_struct_counter)
1967
1968        if self.nameStack[0] == "class":
1969            self.curAccessSpecifier = 'private'
1970        else:#struct
1971            self.curAccessSpecifier = 'public'
1972        debug_print("curAccessSpecifier changed/defaulted to %s"%self.curAccessSpecifier)
1973        if self.nameStack[0] == "union":
1974            newClass = CppUnion(self.nameStack)
1975            if newClass['name'] == 'union ':
1976                self.anon_union_counter = [self.braceDepth, 2]
1977            else:
1978                self.anon_union_counter = [self.braceDepth, 1]
1979            trace_print( 'NEW UNION', newClass['name'] )
1980        else:
1981            newClass = CppClass(self.nameStack, self.curTemplate)
1982            trace_print( 'NEW CLASS', newClass['name'] )
1983        newClass["declaration_method"] = self.nameStack[0]
1984        self.classes_order.append( newClass )    # good idea to save ordering
1985        self.stack = []        # fixes if class declared with ';' in closing brace
1986        if parent:
1987            newClass["namespace"] = self.classes[ parent ]['namespace'] + '::' + parent
1988            newClass['parent'] = parent
1989            self.classes[ parent ]['nested_classes'].append( newClass )
1990            ## supports nested classes with the same name ##
1991            self.curClass = key = parent+'::'+newClass['name']
1992            self._classes_brace_level[ key ] = self.braceDepth
1993
1994        elif newClass['parent']:        # nested class defined outside of parent.  A::B {...}
1995            parent = newClass['parent']
1996            newClass["namespace"] = self.classes[ parent ]['namespace'] + '::' + parent
1997            self.classes[ parent ]['nested_classes'].append( newClass )
1998            ## supports nested classes with the same name ##
1999            self.curClass = key = parent+'::'+newClass['name']
2000            self._classes_brace_level[ key ] = self.braceDepth
2001
2002        else:
2003            newClass["namespace"] = self.cur_namespace()
2004            key = newClass['name']
2005            self.curClass = newClass["name"]
2006            self._classes_brace_level[ newClass['name'] ] = self.braceDepth
2007
2008        if not key.endswith("::") and not key.endswith(" ") and len(key) != 0:
2009            if key in self.classes:
2010                trace_print( 'ERROR name collision:', key )
2011                self.classes[key].show()
2012                trace_print('-'*80)
2013                newClass.show()
2014                assert key not in self.classes    # namespace collision
2015        self.classes[ key ] = newClass
2016        global parseHistory
2017        parseHistory.append({"braceDepth": self.braceDepth, "item_type": "class", "item": newClass})
2018
2019
2020    def evalute_forward_decl(self):
2021        trace_print( 'FORWARD DECL', self.nameStack )
2022        assert self.nameStack[0] in ('class', 'struct')
2023        name = self.nameStack[-1]
2024        if self.curClass:
2025            klass = self.classes[ self.curClass ]
2026            klass['forward_declares'][self.curAccessSpecifier].append( name )
2027            if self.curAccessSpecifier == 'public': klass._public_forward_declares.append( name )
2028        else: self._forward_decls.append( name )
2029
2030class CppHeader( _CppHeader ):
2031    """Parsed C++ class header
2032
2033    Variables produced:
2034    self.classes - Dictionary of classes found in a given header file where the
2035        key is the name of the class
2036    """
2037    IGNORE_NAMES = '__extension__'.split()
2038
2039    def show(self):
2040        for className in list(self.classes.keys()):self.classes[className].show()
2041
2042    def __init__(self, headerFileName, argType="file", **kwargs):
2043        """Create the parsed C++ header file parse tree
2044
2045        headerFileName - Name of the file to parse OR actual file contents (depends on argType)
2046        argType - Indicates how to interpret headerFileName as a file string or file name
2047        kwargs - Supports the following keywords
2048        """
2049        ## reset global state ##
2050        global doxygenCommentCache
2051        doxygenCommentCache = ""
2052        CppVariable.Vars = []
2053        CppStruct.Structs = []
2054
2055        if (argType == "file"):
2056            self.headerFileName = os.path.expandvars(headerFileName)
2057            self.mainClass = os.path.split(self.headerFileName)[1][:-2]
2058            headerFileStr = ""
2059        elif argType == "string":
2060            self.headerFileName = ""
2061            self.mainClass = "???"
2062            headerFileStr = headerFileName
2063        else:
2064            raise Exception("Arg type must be either file or string")
2065        self.curClass = ""
2066
2067        # nested classes have parent::nested, but no extra namespace,
2068        # this keeps the API compatible, TODO proper namespace for everything.
2069        Resolver.CLASSES = {}
2070        self.classes = Resolver.CLASSES
2071        #Functions that are not part of a class
2072        self.functions = []
2073
2074        self.pragmas = []
2075        self.defines = []
2076        self.includes = []
2077        self._precomp_macro_buf = [] #for internal purposes, will end up filling out pragmras and defines at the end
2078
2079        self.enums = []
2080        self.variables = []
2081        self.global_enums = {}
2082        self.nameStack = []
2083        self.nameSpaces = []
2084        self.curAccessSpecifier = 'private'    # private is default
2085        self.curTemplate = None
2086        self.accessSpecifierStack = []
2087        self.accessSpecifierScratch = []
2088        debug_print("curAccessSpecifier changed/defaulted to %s"%self.curAccessSpecifier)
2089        self.initextra()
2090        # Old namestacks for a given level
2091        self.nameStackHistory = []
2092        self.anon_struct_counter = 0
2093        self.anon_union_counter = [-1, 0]
2094        self.templateRegistry = []
2095
2096        if (len(self.headerFileName)):
2097            fd = open(self.headerFileName)
2098            headerFileStr = "".join(fd.readlines())
2099            fd.close()
2100
2101        # Make sure supportedAccessSpecifier are sane
2102        for i in range(0, len(supportedAccessSpecifier)):
2103            if " " not in supportedAccessSpecifier[i]: continue
2104            supportedAccessSpecifier[i] = re.sub("[ ]+", " ", supportedAccessSpecifier[i]).strip()
2105
2106        # Strip out template declarations
2107        templateSectionsToSliceOut = []
2108        try:
2109            for m in re.finditer("template[\t ]*<[^>]*>", headerFileStr):
2110                start = m.start()
2111                # Search for the final '>' which may or may not be caught in the case of nexted <>'s
2112                for i in range(start, len(headerFileStr)):
2113                    if headerFileStr[i] == '<':
2114                        firstBracket = i
2115                        break
2116                ltgtStackCount = 1
2117                #Now look for fianl '>'
2118                for i in range(firstBracket + 1, len(headerFileStr)):
2119                    if headerFileStr[i] == '<':
2120                        ltgtStackCount += 1
2121                    elif headerFileStr[i] == '>':
2122                        ltgtStackCount -= 1
2123                    if ltgtStackCount == 0:
2124                        end = i
2125                        break
2126                templateSectionsToSliceOut.append((start, end))
2127
2128            # Now strip out all instances of the template
2129            templateSectionsToSliceOut.reverse()
2130            for tslice in templateSectionsToSliceOut:
2131                # Replace the template symbol with a single symbol
2132                template_symbol="CppHeaderParser_template_%d"%len(self.templateRegistry)
2133                self.templateRegistry.append(headerFileStr[tslice[0]: tslice[1]+1])
2134                newlines = headerFileStr[tslice[0]: tslice[1]].count("\n") * "\n" #Keep line numbers the same
2135                headerFileStr = headerFileStr[:tslice[0]] + newlines + " " + template_symbol + " " + headerFileStr[tslice[1] + 1:]
2136        except:
2137            pass
2138
2139        # Change multi line #defines and expressions to single lines maintaining line nubmers
2140        # Based from http://stackoverflow.com/questions/2424458/regular-expression-to-match-cs-multiline-preprocessor-statements
2141        matches = re.findall(r'(?m)^(?:.*\\\r?\n)+.*$', headerFileStr)
2142        is_define = re.compile(r'[ \t\v]*#[Dd][Ee][Ff][Ii][Nn][Ee]')
2143        for m in matches:
2144            #Keep the newlines so that linecount doesnt break
2145            num_newlines = len([a for a in m if a=="\n"])
2146            if is_define.match(m):
2147                new_m = m.replace("\n", "<CppHeaderParser_newline_temp_replacement>\\n")
2148            else:
2149                # Just expression taking up multiple lines, make it take 1 line for easier parsing
2150                new_m = m.replace("\\\n", " ")
2151            if (num_newlines > 0):
2152                new_m += "\n"*(num_newlines)
2153            headerFileStr = headerFileStr.replace(m, new_m)
2154
2155        #Filter out Extern "C" statements.  These are order dependent
2156        matches = re.findall(re.compile(r'extern[\t ]+"[Cc]"[\t \n\r]*{', re.DOTALL), headerFileStr)
2157        for m in matches:
2158            #Keep the newlines so that linecount doesnt break
2159            num_newlines = len([a for a in m if a=="\n"])
2160            headerFileStr = headerFileStr.replace(m, "\n" * num_newlines)
2161        headerFileStr = re.sub(r'extern[ ]+"[Cc]"[ ]*', "", headerFileStr)
2162
2163        #Filter out any ignore symbols that end with "()" to account for #define magic functions
2164        for ignore in ignoreSymbols:
2165            if not ignore.endswith("()"): continue
2166            while True:
2167                locStart = headerFileStr.find(ignore[:-1])
2168                if locStart == -1:
2169                    break;
2170                locEnd = None
2171                #Now walk till we find the last paren and account for sub parens
2172                parenCount = 1
2173                inQuotes = False
2174                for i in range(locStart + len(ignore) - 1, len(headerFileStr)):
2175                    c = headerFileStr[i]
2176                    if not inQuotes:
2177                        if c == "(":
2178                            parenCount += 1
2179                        elif c == ")":
2180                            parenCount -= 1
2181                        elif c == '"':
2182                            inQuotes = True
2183                        if parenCount == 0:
2184                            locEnd = i + 1
2185                            break;
2186                    else:
2187                        if c == '"' and headerFileStr[i-1] != '\\':
2188                            inQuotes = False
2189
2190                if locEnd:
2191                    #Strip it out but keep the linecount the same so line numbers are right
2192                    match_str = headerFileStr[locStart:locEnd]
2193                    debug_print("Striping out '%s'"%match_str)
2194                    num_newlines = len([a for a in match_str if a=="\n"])
2195                    headerFileStr = headerFileStr.replace(headerFileStr[locStart:locEnd], "\n"*num_newlines)
2196
2197        self.braceDepth = 0
2198        lex.lex()
2199        lex.input(headerFileStr)
2200        global curLine
2201        global curChar
2202        curLine = 0
2203        curChar = 0
2204        try:
2205            while True:
2206                tok = lex.token()
2207                if not tok: break
2208                if self.anon_union_counter[0] == self.braceDepth and self.anon_union_counter[1]:
2209                    self.anon_union_counter[1] -= 1
2210                tok.value = TagStr(tok.value, lineno=tok.lineno)
2211                #debug_print("TOK: %s"%tok)
2212                if tok.type == 'NAME' and tok.value in self.IGNORE_NAMES: continue
2213                if tok.type != 'TEMPLATE_NAME':
2214                    self.stack.append( tok.value )
2215                curLine = tok.lineno
2216                curChar = tok.lexpos
2217                if (tok.type in ('PRECOMP_MACRO', 'PRECOMP_MACRO_CONT')):
2218                    debug_print("PRECOMP: %s"%tok)
2219                    self._precomp_macro_buf.append(tok.value)
2220                    self.stack = []
2221                    self.nameStack = []
2222                    continue
2223                if tok.type == 'TEMPLATE_NAME':
2224                    try:
2225                        templateId = int(tok.value.replace("CppHeaderParser_template_",""))
2226                        self.curTemplate = self.templateRegistry[templateId]
2227                    except: pass
2228                if (tok.type == 'OPEN_BRACE'):
2229                    if len(self.nameStack) >= 2 and is_namespace(self.nameStack):    # namespace {} with no name used in boost, this sets default?
2230                        if self.nameStack[1] == "__IGNORED_NAMESPACE__CppHeaderParser__":#Used in filtering extern "C"
2231                            self.nameStack[1] = ""
2232                        self.nameSpaces.append(self.nameStack[1])
2233                        ns = self.cur_namespace(); self.stack = []
2234                        if ns not in self.namespaces: self.namespaces.append( ns )
2235                    # Detect special condition of macro magic before class declaration so we
2236                    # can filter it out
2237                    if 'class' in self.nameStack and self.nameStack[0] != 'class':
2238                        classLocationNS = self.nameStack.index("class")
2239                        classLocationS = self.stack.index("class")
2240                        if "(" not in self.nameStack[classLocationNS:]:
2241                            debug_print("keyword 'class' found in unexpected location in nameStack, must be following #define magic.  Process that before moving on")
2242                            origNameStack = self.nameStack
2243                            origStack = self.stack
2244                            #Process first part of stack which is probably #define macro magic and may cause issues
2245                            self.nameStack = self.nameStack[:classLocationNS]
2246                            self.stack = self.stack[:classLocationS]
2247                            try:
2248                                self.evaluate_stack()
2249                            except:
2250                                debug_print("Error processing #define magic... Oh well")
2251                            #Process rest of stack
2252                            self.nameStack = origNameStack[classLocationNS:]
2253                            self.stack = origStack[classLocationS:]
2254
2255
2256                    if len(self.nameStack) and not is_enum_namestack(self.nameStack):
2257                        self.evaluate_stack()
2258                    else:
2259                        self.nameStack.append(tok.value)
2260                    if self.stack and self.stack[0] == 'class': self.stack = []
2261                    self.braceDepth += 1
2262
2263                elif (tok.type == 'CLOSE_BRACE'):
2264                    if self.braceDepth == 0:
2265                        continue
2266                    if (self.braceDepth == len(self.nameSpaces)):
2267                        tmp = self.nameSpaces.pop()
2268                        self.stack = []    # clear stack when namespace ends?
2269                    if len(self.nameStack) and is_enum_namestack(self.nameStack):
2270                        self.nameStack.append(tok.value)
2271                    elif self.braceDepth < 10:
2272                        self.evaluate_stack()
2273                    else:
2274                        self.nameStack = []
2275                    self.braceDepth -= 1
2276                    #self.stack = []; print 'BRACE DEPTH', self.braceDepth, 'NS', len(self.nameSpaces)
2277                    if self.curClass: debug_print( 'CURBD %s'%self._classes_brace_level[ self.curClass ] )
2278
2279                    if (self.braceDepth == 0) or (self.curClass and self._classes_brace_level[self.curClass]==self.braceDepth):
2280                        trace_print( 'END OF CLASS DEF' )
2281                        if self.accessSpecifierStack:
2282                            self.curAccessSpecifier = self.accessSpecifierStack[-1]
2283                            self.accessSpecifierStack = self.accessSpecifierStack[:-1]
2284                        if self.curClass and self.classes[ self.curClass ]['parent']: self.curClass = self.classes[ self.curClass ]['parent']
2285                        else: self.curClass = ""; #self.curStruct = None
2286                        self.stack = []
2287
2288                    #if self.curStruct: self.curStruct = None
2289                    if self.braceDepth == 0 or (self.curStruct and self._structs_brace_level[self.curStruct['type']]==self.braceDepth):
2290                        trace_print( 'END OF STRUCT DEF' )
2291                        self.curStruct = None
2292
2293                    if self._method_body and (self.braceDepth + 1) <= self._method_body:
2294                        self._method_body = None; self.stack = []; self.nameStack = []; trace_print( 'FORCE CLEAR METHBODY' )
2295
2296                if (tok.type == 'OPEN_PAREN'):
2297                    self.nameStack.append(tok.value)
2298                elif (tok.type == 'CLOSE_PAREN'):
2299                    self.nameStack.append(tok.value)
2300                elif (tok.type == 'OPEN_SQUARE_BRACKET'):
2301                    self.nameStack.append(tok.value)
2302                elif (tok.type == 'CLOSE_SQUARE_BRACKET'):
2303                    self.nameStack.append(tok.value)
2304                elif (tok.type == 'TAB'): pass
2305                elif (tok.type == 'EQUALS'):
2306                    self.nameStack.append(tok.value)
2307                elif (tok.type == 'COMMA'):
2308                    self.nameStack.append(tok.value)
2309                elif (tok.type == 'BACKSLASH'):
2310                    self.nameStack.append(tok.value)
2311                elif (tok.type == 'DIVIDE'):
2312                    self.nameStack.append(tok.value)
2313                elif (tok.type == 'PIPE'):
2314                    self.nameStack.append(tok.value)
2315                elif (tok.type == 'PERCENT'):
2316                    self.nameStack.append(tok.value)
2317                elif (tok.type == 'CARET'):
2318                    self.nameStack.append(tok.value)
2319                elif (tok.type == 'EXCLAMATION'):
2320                    self.nameStack.append(tok.value)
2321                elif (tok.type == 'SQUOTE'): pass
2322                elif (tok.type == 'NUMBER' or tok.type == 'FLOAT_NUMBER'):
2323                    self.nameStack.append(tok.value)
2324                elif (tok.type == 'MINUS'):
2325                    self.nameStack.append(tok.value)
2326                elif (tok.type == 'PLUS'):
2327                    self.nameStack.append(tok.value)
2328                elif (tok.type == 'STRING_LITERAL'):
2329                    self.nameStack.append(tok.value)
2330                elif (tok.type == 'NAME' or tok.type == 'AMPERSTAND' or tok.type == 'ASTERISK' or tok.type == 'CHAR_LITERAL'):
2331                    if tok.value in ignoreSymbols:
2332                        debug_print("Ignore symbol %s"%tok.value)
2333                    elif (tok.value == 'class'):
2334                        self.nameStack.append(tok.value)
2335                    elif tok.value in supportedAccessSpecifier:
2336                        if len(self.nameStack) and self.nameStack[0] in ("class", "struct", "union"):
2337                            self.nameStack.append(tok.value)
2338                        elif self.braceDepth == len(self.nameSpaces) + 1 or self.braceDepth == (len(self.nameSpaces) + len(self.curClass.split("::"))):
2339                            self.curAccessSpecifier = tok.value;
2340                            self.accessSpecifierScratch.append(tok.value)
2341                            debug_print("curAccessSpecifier updated to %s"%self.curAccessSpecifier)
2342                        self.stack = []
2343                    else:
2344                        self.nameStack.append(tok.value)
2345                        if self.anon_union_counter[0] == self.braceDepth:
2346                            self.anon_union_counter = [-1, 0]
2347                elif (tok.type == 'COLON'):
2348                    #Dont want colon to be first in stack
2349                    if len(self.nameStack) == 0:
2350                        self.accessSpecifierScratch = []
2351                        continue
2352
2353                    # Handle situation where access specifiers can be multi words such as "public slots"
2354                    jns = " ".join(self.accessSpecifierScratch + self.nameStack)
2355                    if jns in supportedAccessSpecifier:
2356                        self.curAccessSpecifier = jns;
2357                        debug_print("curAccessSpecifier updated to %s"%self.curAccessSpecifier)
2358                        self.stack = []
2359                        self.nameStack = []
2360                    else:
2361                        self.nameStack.append(tok.value)
2362                    self.accessSpecifierScratch = []
2363
2364                elif (tok.type == 'SEMI_COLON'):
2365                    if self.anon_union_counter[0] == self.braceDepth and self.anon_union_counter[1]:
2366                        debug_print("Creating anonymous union")
2367                        #Force the processing of an anonymous union
2368                        saved_namestack = self.nameStack[:]
2369                        saved_stack = self.stack[:]
2370                        self.nameStack = [""]
2371                        self.stack = self.nameStack + [";"]
2372                        self.nameStack = self.nameStack[0:1]
2373                        debug_print("pre eval anon stack")
2374                        self.evaluate_stack( tok.type )
2375                        debug_print("post eval anon stack")
2376                        self.nameStack = saved_namestack
2377                        self.stack = saved_stack
2378                        self.anon_union_counter = [-1, 0];
2379
2380
2381                    if (self.braceDepth < 10): self.evaluate_stack( tok.type )
2382                    self.stack = []
2383                    self.nameStack = []
2384
2385        except:
2386            if (debug): raise
2387            raise CppParseError("Not able to parse %s on line %d evaluating \"%s\"\nError around: %s"
2388                                % (self.headerFileName, tok.lineno, tok.value, " ".join(self.nameStack)))
2389
2390        self.finalize()
2391        global parseHistory
2392        parseHistory = []
2393        # Delete some temporary variables
2394        for key in ["_precomp_macro_buf", "nameStack", "nameSpaces", "curAccessSpecifier", "accessSpecifierStack",
2395            "accessSpecifierScratch", "nameStackHistory", "anon_struct_counter", "anon_union_counter",
2396            "_classes_brace_level", "_forward_decls", "stack", "mainClass", "curStruct", "_template_typenames",
2397            "_method_body", "braceDepth", "_structs_brace_level", "typedefs_order", "curTemplate", "templateRegistry"]:
2398            del self.__dict__[key]
2399
2400
2401    def evaluate_stack(self, token=None):
2402        """Evaluates the current name stack"""
2403        global doxygenCommentCache
2404
2405        self.nameStack = filter_out_attribute_keyword(self.nameStack)
2406        self.stack = filter_out_attribute_keyword(self.stack)
2407        nameStackCopy = self.nameStack[:]
2408
2409        debug_print( "Evaluating stack %s\n       BraceDepth: %s (called from %d)" %(self.nameStack,self.braceDepth, inspect.currentframe().f_back.f_lineno))
2410
2411        #Handle special case of overloading operator ()
2412        if "operator()(" in "".join(self.nameStack):
2413            operator_index = self.nameStack.index("operator")
2414            self.nameStack.pop(operator_index + 2)
2415            self.nameStack.pop(operator_index + 1)
2416            self.nameStack[operator_index] = "operator()"
2417
2418        if (len(self.curClass)):
2419            debug_print( "%s (%s) "%(self.curClass, self.curAccessSpecifier))
2420        else:
2421            debug_print( "<anonymous> (%s) "%self.curAccessSpecifier)
2422
2423        #Filter special case of array with casting in it
2424        try:
2425            bracePos = self.nameStack.index("[")
2426            parenPos = self.nameStack.index("(")
2427            if bracePos == parenPos - 1:
2428                endParen = self.nameStack.index(")")
2429                self.nameStack = self.nameStack[:bracePos + 1] + self.nameStack[endParen + 1:]
2430                debug_print("Filtered namestack to=%s"%self.nameStack)
2431        except: pass
2432
2433        #if 'typedef' in self.nameStack: self.evaluate_typedef()        # allows nested typedefs, probably a bad idea
2434        if (not self.curClass and 'typedef' in self.nameStack and
2435            (('struct' not in self.nameStack and 'union' not in self.nameStack) or self.stack[-1] == ";") and
2436            not is_enum_namestack(self.nameStack)):
2437            trace_print('STACK', self.stack)
2438            self.evaluate_typedef()
2439            return
2440
2441        elif (len(self.nameStack) == 0):
2442            debug_print( "trace" )
2443            debug_print( "(Empty Stack)" )
2444            return
2445        elif (self.nameStack[0] == "namespace"):
2446            #Taken care of outside of here
2447            pass
2448        elif len(self.nameStack) == 2 and self.nameStack[0] == "friend":#friend class declaration
2449            pass
2450        elif len(self.nameStack) >= 2 and self.nameStack[0] == 'using' and self.nameStack[1] == 'namespace': pass    # TODO
2451
2452        elif is_enum_namestack(self.nameStack):
2453            debug_print( "trace" )
2454            self.evaluate_enum_stack()
2455
2456        elif self._method_body and (self.braceDepth + 1) > self._method_body: trace_print( 'INSIDE METHOD DEF' )
2457        elif is_method_namestack(self.stack) and not self.curStruct and '(' in self.nameStack:
2458            debug_print( "trace" )
2459            if self.braceDepth > 0:
2460                if "{" in self.stack and self.stack[0] != '{' and self.stack[-1] == ';' and self.braceDepth == 1:
2461                    #Special case of a method defined outside a class that has a body
2462                    pass
2463                else:
2464                    self.evaluate_method_stack()
2465            else:
2466                #Free function
2467                self.evaluate_method_stack()
2468        elif (len(self.nameStack) == 1 and len(self.nameStackHistory) > self.braceDepth
2469              and (self.nameStackHistory[self.braceDepth][0][0:2] == ["typedef", "struct"] or
2470                   self.nameStackHistory[self.braceDepth][0][0:2] == ["typedef", "union"])):
2471            # Look for the name of a typedef struct: struct typedef {...] StructName; or unions to get renamed
2472            debug_print("found the naming of a union")
2473            type_name_to_rename = self.nameStackHistory[self.braceDepth][1]
2474            new_name = self.nameStack[0]
2475            type_to_rename = self.classes[type_name_to_rename]
2476            type_to_rename["name"] = self.nameStack[0]
2477            #Now re install it in its new location
2478            self.classes[new_name] = type_to_rename
2479            if new_name != type_name_to_rename:
2480                del self.classes[type_name_to_rename]
2481        elif is_property_namestack(self.nameStack) and self.stack[-1] == ';':
2482            debug_print( "trace" )
2483            if self.nameStack[0] in ('class', 'struct') and len(self.stack) == 3: self.evalute_forward_decl()
2484            elif len(self.nameStack) >= 2 and (self.nameStack[0]=='friend' and self.nameStack[1]=='class'): pass
2485            else: self.evaluate_property_stack()    # catches class props and structs in a namespace
2486
2487        elif self.nameStack[0] in ("class", "struct", "union") or self.nameStack[0] == 'typedef' and self.nameStack[1] in ('struct', 'union'):
2488            #Parsing a union can reuse much of the class parsing
2489            debug_print( "trace" )
2490            self.evaluate_class_stack()
2491
2492        elif not self.curClass:
2493            debug_print( "trace" )
2494            if is_enum_namestack(self.nameStack): self.evaluate_enum_stack()
2495            elif self.curStruct and self.stack[-1] == ';': self.evaluate_property_stack()    # this catches fields of global structs
2496            self.nameStack = []
2497            doxygenCommentCache = ""
2498        elif (self.braceDepth < 1):
2499            debug_print( "trace" )
2500            #Ignore global stuff for now
2501            debug_print( "Global stuff: %s"%self.nameStack )
2502            self.nameStack = []
2503            doxygenCommentCache = ""
2504        elif (self.braceDepth > len(self.nameSpaces) + 1):
2505            debug_print( "trace" )
2506            self.nameStack = []
2507            doxygenCommentCache = ""
2508
2509        try:
2510            self.nameStackHistory[self.braceDepth] = (nameStackCopy, self.curClass)
2511        except:
2512            self.nameStackHistory.append((nameStackCopy, self.curClass))
2513        self.nameStack = []        # its a little confusing to have some if/else above return and others not, and then clearning the nameStack down here
2514        doxygenCommentCache = ""
2515        self.curTemplate = None
2516
2517
2518    def evaluate_enum_stack(self):
2519        """Create an Enum out of the name stack"""
2520        debug_print( "evaluating enum" )
2521        newEnum = CppEnum(self.nameStack)
2522        if len(list(newEnum.keys())):
2523            if len(self.curClass):
2524                newEnum["namespace"] = self.cur_namespace(False)
2525                klass = self.classes[self.curClass]
2526                klass["enums"][self.curAccessSpecifier].append(newEnum)
2527                if self.curAccessSpecifier == 'public' and 'name' in newEnum: klass._public_enums[ newEnum['name'] ] = newEnum
2528            else:
2529                newEnum["namespace"] = self.cur_namespace(True)
2530                self.enums.append(newEnum)
2531                if 'name' in newEnum and newEnum['name']: self.global_enums[ newEnum['name'] ] = newEnum
2532
2533            #This enum has instances, turn them into properties
2534            if "instances" in newEnum:
2535                instanceType = "enum"
2536                if "name" in newEnum:
2537                    instanceType = newEnum["name"]
2538                for instance in newEnum["instances"]:
2539                    self.nameStack = [instanceType,  instance]
2540                    self.evaluate_property_stack()
2541                del newEnum["instances"]
2542
2543    def strip_parent_keys(self):
2544        """Strip all parent (and method) keys to prevent loops"""
2545        obj_queue = [self]
2546        while len(obj_queue):
2547            obj = obj_queue.pop()
2548            trace_print("pop %s type %s"%(obj, type(obj)))
2549            try:
2550                if "parent" in obj.keys():
2551                    del obj["parent"]
2552                    trace_print("Stripped parent from %s"%obj.keys())
2553            except: pass
2554            try:
2555                if "method" in obj.keys():
2556                    del obj["method"]
2557                    trace_print("Stripped method from %s"%obj.keys())
2558            except: pass
2559            # Figure out what sub types are one of ours
2560            try:
2561                if not hasattr(obj, 'keys'):
2562                    obj = obj.__dict__
2563                for k in obj.keys():
2564                    trace_print("-Try key %s"%(k))
2565                    trace_print("-type  %s"%(type(obj[k])))
2566                    if k in ["nameStackHistory", "parent", "_public_typedefs"]: continue
2567                    if type(obj[k]) == list:
2568                        for i in obj[k]:
2569                            trace_print("push l %s"%i)
2570                            obj_queue.append(i)
2571                    elif type(obj[k]) == dict:
2572                        if len(obj):
2573                            trace_print("push d %s"%obj[k])
2574                            obj_queue.append(obj[k])
2575                    elif type(obj[k]) == type(type(0)):
2576                        if type(obj[k]) == int:
2577                            obj[k] = "int"
2578                        elif type(obj[k]) == str:
2579                            obj[k] = "string"
2580                        else:
2581                            obj[k] = "???"
2582                    trace_print("next key\n")
2583            except:
2584                trace_print("Exception")
2585
2586    def toJSON(self, indent=4):
2587        """Converts a parsed structure to JSON"""
2588        import json
2589        self.strip_parent_keys()
2590        try:
2591            del self.__dict__["classes_order"]
2592        except: pass
2593        return json.dumps(self.__dict__, indent=indent)
2594
2595
2596    def __repr__(self):
2597        rtn = {
2598          "classes": self.classes,
2599          "functions": self.functions,
2600          "enums": self.enums,
2601          "variables": self.variables,
2602        }
2603        return repr(rtn)
2604
2605    def __str__(self):
2606        rtn = ""
2607        for className in list(self.classes.keys()):
2608            rtn += "%s\n"%self.classes[className]
2609        if self.functions:
2610            rtn += "// functions\n"
2611            for f in self.functions:
2612                rtn += "%s\n"%f
2613        if self.variables:
2614            rtn += "// variables\n"
2615            for f in self.variables:
2616                rtn += "%s\n"%f
2617        if self.enums:
2618            rtn += "// enums\n"
2619            for f in self.enums:
2620                rtn += "%s\n"%f
2621        return rtn
2622