summaryrefslogtreecommitdiffstats
path: root/src/googletest/googlemock/scripts/generator
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
commit19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch)
tree42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/googletest/googlemock/scripts/generator
parentInitial commit. (diff)
downloadceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.tar.xz
ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.zip
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/googletest/googlemock/scripts/generator')
-rw-r--r--src/googletest/googlemock/scripts/generator/LICENSE203
-rw-r--r--src/googletest/googlemock/scripts/generator/README34
-rw-r--r--src/googletest/googlemock/scripts/generator/README.cppclean115
-rwxr-xr-xsrc/googletest/googlemock/scripts/generator/cpp/__init__.py0
-rwxr-xr-xsrc/googletest/googlemock/scripts/generator/cpp/ast.py1772
-rwxr-xr-xsrc/googletest/googlemock/scripts/generator/cpp/gmock_class.py245
-rwxr-xr-xsrc/googletest/googlemock/scripts/generator/cpp/gmock_class_test.py552
-rwxr-xr-xsrc/googletest/googlemock/scripts/generator/cpp/keywords.py56
-rwxr-xr-xsrc/googletest/googlemock/scripts/generator/cpp/tokenize.py284
-rwxr-xr-xsrc/googletest/googlemock/scripts/generator/cpp/utils.py37
-rwxr-xr-xsrc/googletest/googlemock/scripts/generator/gmock_gen.py30
11 files changed, 3328 insertions, 0 deletions
diff --git a/src/googletest/googlemock/scripts/generator/LICENSE b/src/googletest/googlemock/scripts/generator/LICENSE
new file mode 100644
index 000000000..87ea06365
--- /dev/null
+++ b/src/googletest/googlemock/scripts/generator/LICENSE
@@ -0,0 +1,203 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [2007] Neal Norwitz
+ Portions Copyright [2007] Google Inc.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/src/googletest/googlemock/scripts/generator/README b/src/googletest/googlemock/scripts/generator/README
new file mode 100644
index 000000000..01fd463dd
--- /dev/null
+++ b/src/googletest/googlemock/scripts/generator/README
@@ -0,0 +1,34 @@
+
+The Google Mock class generator is an application that is part of cppclean.
+For more information about cppclean, visit http://code.google.com/p/cppclean/
+
+The mock generator requires Python 2.3.5 or later. If you don't have Python
+installed on your system, you will also need to install it. You can download
+Python from: http://www.python.org/download/releases/
+
+To use the Google Mock class generator, you need to call it
+on the command line passing the header file and class for which you want
+to generate a Google Mock class.
+
+Make sure to install the scripts somewhere in your path. Then you can
+run the program.
+
+ gmock_gen.py header-file.h [ClassName]...
+
+If no ClassNames are specified, all classes in the file are emitted.
+
+To change the indentation from the default of 2, set INDENT in
+the environment. For example to use an indent of 4 spaces:
+
+INDENT=4 gmock_gen.py header-file.h ClassName
+
+This version was made from SVN revision 281 in the cppclean repository.
+
+Known Limitations
+-----------------
+Not all code will be generated properly. For example, when mocking templated
+classes, the template information is lost. You will need to add the template
+information manually.
+
+Not all permutations of using multiple pointers/references will be rendered
+properly. These will also have to be fixed manually.
diff --git a/src/googletest/googlemock/scripts/generator/README.cppclean b/src/googletest/googlemock/scripts/generator/README.cppclean
new file mode 100644
index 000000000..65431b617
--- /dev/null
+++ b/src/googletest/googlemock/scripts/generator/README.cppclean
@@ -0,0 +1,115 @@
+Goal:
+-----
+ CppClean attempts to find problems in C++ source that slow development
+ in large code bases, for example various forms of unused code.
+ Unused code can be unused functions, methods, data members, types, etc
+ to unnecessary #include directives. Unnecessary #includes can cause
+ considerable extra compiles increasing the edit-compile-run cycle.
+
+ The project home page is: http://code.google.com/p/cppclean/
+
+
+Features:
+---------
+ * Find and print C++ language constructs: classes, methods, functions, etc.
+ * Find classes with virtual methods, no virtual destructor, and no bases
+ * Find global/static data that are potential problems when using threads
+ * Unnecessary forward class declarations
+ * Unnecessary function declarations
+ * Undeclared function definitions
+ * (planned) Find unnecessary header files #included
+ - No direct reference to anything in the header
+ - Header is unnecessary if classes were forward declared instead
+ * (planned) Source files that reference headers not directly #included,
+ ie, files that rely on a transitive #include from another header
+ * (planned) Unused members (private, protected, & public) methods and data
+ * (planned) Store AST in a SQL database so relationships can be queried
+
+AST is Abstract Syntax Tree, a representation of parsed source code.
+http://en.wikipedia.org/wiki/Abstract_syntax_tree
+
+
+System Requirements:
+--------------------
+ * Python 2.4 or later (2.3 probably works too)
+ * Works on Windows (untested), Mac OS X, and Unix
+
+
+How to Run:
+-----------
+ For all examples, it is assumed that cppclean resides in a directory called
+ /cppclean.
+
+ To print warnings for classes with virtual methods, no virtual destructor and
+ no base classes:
+
+ /cppclean/run.sh nonvirtual_dtors.py file1.h file2.h file3.cc ...
+
+ To print all the functions defined in header file(s):
+
+ /cppclean/run.sh functions.py file1.h file2.h ...
+
+ All the commands take multiple files on the command line. Other programs
+ include: find_warnings, headers, methods, and types. Some other programs
+ are available, but used primarily for debugging.
+
+ run.sh is a simple wrapper that sets PYTHONPATH to /cppclean and then
+ runs the program in /cppclean/cpp/PROGRAM.py. There is currently
+ no equivalent for Windows. Contributions for a run.bat file
+ would be greatly appreciated.
+
+
+How to Configure:
+-----------------
+ You can add a siteheaders.py file in /cppclean/cpp to configure where
+ to look for other headers (typically -I options passed to a compiler).
+ Currently two values are supported: _TRANSITIVE and GetIncludeDirs.
+ _TRANSITIVE should be set to a boolean value (True or False) indicating
+ whether to transitively process all header files. The default is False.
+
+ GetIncludeDirs is a function that takes a single argument and returns
+ a sequence of directories to include. This can be a generator or
+ return a static list.
+
+ def GetIncludeDirs(filename):
+ return ['/some/path/with/other/headers']
+
+ # Here is a more complicated example.
+ def GetIncludeDirs(filename):
+ yield '/path1'
+ yield os.path.join('/path2', os.path.dirname(filename))
+ yield '/path3'
+
+
+How to Test:
+------------
+ For all examples, it is assumed that cppclean resides in a directory called
+ /cppclean. The tests require
+
+ cd /cppclean
+ make test
+ # To generate expected results after a change:
+ make expected
+
+
+Current Status:
+---------------
+ The parser works pretty well for header files, parsing about 99% of Google's
+ header files. Anything which inspects structure of C++ source files should
+ work reasonably well. Function bodies are not transformed to an AST,
+ but left as tokens. Much work is still needed on finding unused header files
+ and storing an AST in a database.
+
+
+Non-goals:
+----------
+ * Parsing all valid C++ source
+ * Handling invalid C++ source gracefully
+ * Compiling to machine code (or anything beyond an AST)
+
+
+Contact:
+--------
+ If you used cppclean, I would love to hear about your experiences
+ cppclean@googlegroups.com. Even if you don't use cppclean, I'd like to
+ hear from you. :-) (You can contact me directly at: nnorwitz@gmail.com)
diff --git a/src/googletest/googlemock/scripts/generator/cpp/__init__.py b/src/googletest/googlemock/scripts/generator/cpp/__init__.py
new file mode 100755
index 000000000..e69de29bb
--- /dev/null
+++ b/src/googletest/googlemock/scripts/generator/cpp/__init__.py
diff --git a/src/googletest/googlemock/scripts/generator/cpp/ast.py b/src/googletest/googlemock/scripts/generator/cpp/ast.py
new file mode 100755
index 000000000..cc9f89aa5
--- /dev/null
+++ b/src/googletest/googlemock/scripts/generator/cpp/ast.py
@@ -0,0 +1,1772 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Neal Norwitz
+# Portions Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Generate an Abstract Syntax Tree (AST) for C++."""
+
+# FIXME:
+# * Tokens should never be exported, need to convert to Nodes
+# (return types, parameters, etc.)
+# * Handle static class data for templatized classes
+# * Handle casts (both C++ and C-style)
+# * Handle conditions and loops (if/else, switch, for, while/do)
+#
+# TODO much, much later:
+# * Handle #define
+# * exceptions
+
+
+try:
+ # Python 3.x
+ import builtins
+except ImportError:
+ # Python 2.x
+ import __builtin__ as builtins
+
+import sys
+import traceback
+
+from cpp import keywords
+from cpp import tokenize
+from cpp import utils
+
+
+if not hasattr(builtins, 'reversed'):
+ # Support Python 2.3 and earlier.
+ def reversed(seq):
+ for i in range(len(seq)-1, -1, -1):
+ yield seq[i]
+
+if not hasattr(builtins, 'next'):
+ # Support Python 2.5 and earlier.
+ def next(obj):
+ return obj.next()
+
+
+VISIBILITY_PUBLIC, VISIBILITY_PROTECTED, VISIBILITY_PRIVATE = range(3)
+
+FUNCTION_NONE = 0x00
+FUNCTION_CONST = 0x01
+FUNCTION_VIRTUAL = 0x02
+FUNCTION_PURE_VIRTUAL = 0x04
+FUNCTION_CTOR = 0x08
+FUNCTION_DTOR = 0x10
+FUNCTION_ATTRIBUTE = 0x20
+FUNCTION_UNKNOWN_ANNOTATION = 0x40
+FUNCTION_THROW = 0x80
+FUNCTION_OVERRIDE = 0x100
+
+"""
+These are currently unused. Should really handle these properly at some point.
+
+TYPE_MODIFIER_INLINE = 0x010000
+TYPE_MODIFIER_EXTERN = 0x020000
+TYPE_MODIFIER_STATIC = 0x040000
+TYPE_MODIFIER_CONST = 0x080000
+TYPE_MODIFIER_REGISTER = 0x100000
+TYPE_MODIFIER_VOLATILE = 0x200000
+TYPE_MODIFIER_MUTABLE = 0x400000
+
+TYPE_MODIFIER_MAP = {
+ 'inline': TYPE_MODIFIER_INLINE,
+ 'extern': TYPE_MODIFIER_EXTERN,
+ 'static': TYPE_MODIFIER_STATIC,
+ 'const': TYPE_MODIFIER_CONST,
+ 'register': TYPE_MODIFIER_REGISTER,
+ 'volatile': TYPE_MODIFIER_VOLATILE,
+ 'mutable': TYPE_MODIFIER_MUTABLE,
+ }
+"""
+
+_INTERNAL_TOKEN = 'internal'
+_NAMESPACE_POP = 'ns-pop'
+
+
+# TODO(nnorwitz): use this as a singleton for templated_types, etc
+# where we don't want to create a new empty dict each time. It is also const.
+class _NullDict(object):
+ __contains__ = lambda self: False
+ keys = values = items = iterkeys = itervalues = iteritems = lambda self: ()
+
+
+# TODO(nnorwitz): move AST nodes into a separate module.
+class Node(object):
+ """Base AST node."""
+
+ def __init__(self, start, end):
+ self.start = start
+ self.end = end
+
+ def IsDeclaration(self):
+ """Returns bool if this node is a declaration."""
+ return False
+
+ def IsDefinition(self):
+ """Returns bool if this node is a definition."""
+ return False
+
+ def IsExportable(self):
+ """Returns bool if this node exportable from a header file."""
+ return False
+
+ def Requires(self, node):
+ """Does this AST node require the definition of the node passed in?"""
+ return False
+
+ def XXX__str__(self):
+ return self._StringHelper(self.__class__.__name__, '')
+
+ def _StringHelper(self, name, suffix):
+ if not utils.DEBUG:
+ return '%s(%s)' % (name, suffix)
+ return '%s(%d, %d, %s)' % (name, self.start, self.end, suffix)
+
+ def __repr__(self):
+ return str(self)
+
+
+class Define(Node):
+ def __init__(self, start, end, name, definition):
+ Node.__init__(self, start, end)
+ self.name = name
+ self.definition = definition
+
+ def __str__(self):
+ value = '%s %s' % (self.name, self.definition)
+ return self._StringHelper(self.__class__.__name__, value)
+
+
+class Include(Node):
+ def __init__(self, start, end, filename, system):
+ Node.__init__(self, start, end)
+ self.filename = filename
+ self.system = system
+
+ def __str__(self):
+ fmt = '"%s"'
+ if self.system:
+ fmt = '<%s>'
+ return self._StringHelper(self.__class__.__name__, fmt % self.filename)
+
+
+class Goto(Node):
+ def __init__(self, start, end, label):
+ Node.__init__(self, start, end)
+ self.label = label
+
+ def __str__(self):
+ return self._StringHelper(self.__class__.__name__, str(self.label))
+
+
+class Expr(Node):
+ def __init__(self, start, end, expr):
+ Node.__init__(self, start, end)
+ self.expr = expr
+
+ def Requires(self, node):
+ # TODO(nnorwitz): impl.
+ return False
+
+ def __str__(self):
+ return self._StringHelper(self.__class__.__name__, str(self.expr))
+
+
+class Return(Expr):
+ pass
+
+
+class Delete(Expr):
+ pass
+
+
+class Friend(Expr):
+ def __init__(self, start, end, expr, namespace):
+ Expr.__init__(self, start, end, expr)
+ self.namespace = namespace[:]
+
+
+class Using(Node):
+ def __init__(self, start, end, names):
+ Node.__init__(self, start, end)
+ self.names = names
+
+ def __str__(self):
+ return self._StringHelper(self.__class__.__name__, str(self.names))
+
+
+class Parameter(Node):
+ def __init__(self, start, end, name, parameter_type, default):
+ Node.__init__(self, start, end)
+ self.name = name
+ self.type = parameter_type
+ self.default = default
+
+ def Requires(self, node):
+ # TODO(nnorwitz): handle namespaces, etc.
+ return self.type.name == node.name
+
+ def __str__(self):
+ name = str(self.type)
+ suffix = '%s %s' % (name, self.name)
+ if self.default:
+ suffix += ' = ' + ''.join([d.name for d in self.default])
+ return self._StringHelper(self.__class__.__name__, suffix)
+
+
+class _GenericDeclaration(Node):
+ def __init__(self, start, end, name, namespace):
+ Node.__init__(self, start, end)
+ self.name = name
+ self.namespace = namespace[:]
+
+ def FullName(self):
+ prefix = ''
+ if self.namespace and self.namespace[-1]:
+ prefix = '::'.join(self.namespace) + '::'
+ return prefix + self.name
+
+ def _TypeStringHelper(self, suffix):
+ if self.namespace:
+ names = [n or '<anonymous>' for n in self.namespace]
+ suffix += ' in ' + '::'.join(names)
+ return self._StringHelper(self.__class__.__name__, suffix)
+
+
+# TODO(nnorwitz): merge with Parameter in some way?
+class VariableDeclaration(_GenericDeclaration):
+ def __init__(self, start, end, name, var_type, initial_value, namespace):
+ _GenericDeclaration.__init__(self, start, end, name, namespace)
+ self.type = var_type
+ self.initial_value = initial_value
+
+ def Requires(self, node):
+ # TODO(nnorwitz): handle namespaces, etc.
+ return self.type.name == node.name
+
+ def ToString(self):
+ """Return a string that tries to reconstitute the variable decl."""
+ suffix = '%s %s' % (self.type, self.name)
+ if self.initial_value:
+ suffix += ' = ' + self.initial_value
+ return suffix
+
+ def __str__(self):
+ return self._StringHelper(self.__class__.__name__, self.ToString())
+
+
+class Typedef(_GenericDeclaration):
+ def __init__(self, start, end, name, alias, namespace):
+ _GenericDeclaration.__init__(self, start, end, name, namespace)
+ self.alias = alias
+
+ def IsDefinition(self):
+ return True
+
+ def IsExportable(self):
+ return True
+
+ def Requires(self, node):
+ # TODO(nnorwitz): handle namespaces, etc.
+ name = node.name
+ for token in self.alias:
+ if token is not None and name == token.name:
+ return True
+ return False
+
+ def __str__(self):
+ suffix = '%s, %s' % (self.name, self.alias)
+ return self._TypeStringHelper(suffix)
+
+
+class _NestedType(_GenericDeclaration):
+ def __init__(self, start, end, name, fields, namespace):
+ _GenericDeclaration.__init__(self, start, end, name, namespace)
+ self.fields = fields
+
+ def IsDefinition(self):
+ return True
+
+ def IsExportable(self):
+ return True
+
+ def __str__(self):
+ suffix = '%s, {%s}' % (self.name, self.fields)
+ return self._TypeStringHelper(suffix)
+
+
+class Union(_NestedType):
+ pass
+
+
+class Enum(_NestedType):
+ pass
+
+
+class Class(_GenericDeclaration):
+ def __init__(self, start, end, name, bases, templated_types, body, namespace):
+ _GenericDeclaration.__init__(self, start, end, name, namespace)
+ self.bases = bases
+ self.body = body
+ self.templated_types = templated_types
+
+ def IsDeclaration(self):
+ return self.bases is None and self.body is None
+
+ def IsDefinition(self):
+ return not self.IsDeclaration()
+
+ def IsExportable(self):
+ return not self.IsDeclaration()
+
+ def Requires(self, node):
+ # TODO(nnorwitz): handle namespaces, etc.
+ if self.bases:
+ for token_list in self.bases:
+ # TODO(nnorwitz): bases are tokens, do name comparision.
+ for token in token_list:
+ if token.name == node.name:
+ return True
+ # TODO(nnorwitz): search in body too.
+ return False
+
+ def __str__(self):
+ name = self.name
+ if self.templated_types:
+ name += '<%s>' % self.templated_types
+ suffix = '%s, %s, %s' % (name, self.bases, self.body)
+ return self._TypeStringHelper(suffix)
+
+
+class Struct(Class):
+ pass
+
+
+class Function(_GenericDeclaration):
+ def __init__(self, start, end, name, return_type, parameters,
+ modifiers, templated_types, body, namespace):
+ _GenericDeclaration.__init__(self, start, end, name, namespace)
+ converter = TypeConverter(namespace)
+ self.return_type = converter.CreateReturnType(return_type)
+ self.parameters = converter.ToParameters(parameters)
+ self.modifiers = modifiers
+ self.body = body
+ self.templated_types = templated_types
+
+ def IsDeclaration(self):
+ return self.body is None
+
+ def IsDefinition(self):
+ return self.body is not None
+
+ def IsExportable(self):
+ if self.return_type and 'static' in self.return_type.modifiers:
+ return False
+ return None not in self.namespace
+
+ def Requires(self, node):
+ if self.parameters:
+ # TODO(nnorwitz): parameters are tokens, do name comparision.
+ for p in self.parameters:
+ if p.name == node.name:
+ return True
+ # TODO(nnorwitz): search in body too.
+ return False
+
+ def __str__(self):
+ # TODO(nnorwitz): add templated_types.
+ suffix = ('%s %s(%s), 0x%02x, %s' %
+ (self.return_type, self.name, self.parameters,
+ self.modifiers, self.body))
+ return self._TypeStringHelper(suffix)
+
+
+class Method(Function):
+ def __init__(self, start, end, name, in_class, return_type, parameters,
+ modifiers, templated_types, body, namespace):
+ Function.__init__(self, start, end, name, return_type, parameters,
+ modifiers, templated_types, body, namespace)
+ # TODO(nnorwitz): in_class could also be a namespace which can
+ # mess up finding functions properly.
+ self.in_class = in_class
+
+
+class Type(_GenericDeclaration):
+ """Type used for any variable (eg class, primitive, struct, etc)."""
+
+ def __init__(self, start, end, name, templated_types, modifiers,
+ reference, pointer, array):
+ """
+ Args:
+ name: str name of main type
+ templated_types: [Class (Type?)] template type info between <>
+ modifiers: [str] type modifiers (keywords) eg, const, mutable, etc.
+ reference, pointer, array: bools
+ """
+ _GenericDeclaration.__init__(self, start, end, name, [])
+ self.templated_types = templated_types
+ if not name and modifiers:
+ self.name = modifiers.pop()
+ self.modifiers = modifiers
+ self.reference = reference
+ self.pointer = pointer
+ self.array = array
+
+ def __str__(self):
+ prefix = ''
+ if self.modifiers:
+ prefix = ' '.join(self.modifiers) + ' '
+ name = str(self.name)
+ if self.templated_types:
+ name += '<%s>' % self.templated_types
+ suffix = prefix + name
+ if self.reference:
+ suffix += '&'
+ if self.pointer:
+ suffix += '*'
+ if self.array:
+ suffix += '[]'
+ return self._TypeStringHelper(suffix)
+
+ # By definition, Is* are always False. A Type can only exist in
+ # some sort of variable declaration, parameter, or return value.
+ def IsDeclaration(self):
+ return False
+
+ def IsDefinition(self):
+ return False
+
+ def IsExportable(self):
+ return False
+
+
+class TypeConverter(object):
+
+ def __init__(self, namespace_stack):
+ self.namespace_stack = namespace_stack
+
+ def _GetTemplateEnd(self, tokens, start):
+ count = 1
+ end = start
+ while 1:
+ token = tokens[end]
+ end += 1
+ if token.name == '<':
+ count += 1
+ elif token.name == '>':
+ count -= 1
+ if count == 0:
+ break
+ return tokens[start:end-1], end
+
+ def ToType(self, tokens):
+ """Convert [Token,...] to [Class(...), ] useful for base classes.
+ For example, code like class Foo : public Bar<x, y> { ... };
+ the "Bar<x, y>" portion gets converted to an AST.
+
+ Returns:
+ [Class(...), ...]
+ """
+ result = []
+ name_tokens = []
+ reference = pointer = array = False
+
+ def AddType(templated_types):
+ # Partition tokens into name and modifier tokens.
+ names = []
+ modifiers = []
+ for t in name_tokens:
+ if keywords.IsKeyword(t.name):
+ modifiers.append(t.name)
+ else:
+ names.append(t.name)
+ name = ''.join(names)
+ if name_tokens:
+ result.append(Type(name_tokens[0].start, name_tokens[-1].end,
+ name, templated_types, modifiers,
+ reference, pointer, array))
+ del name_tokens[:]
+
+ i = 0
+ end = len(tokens)
+ while i < end:
+ token = tokens[i]
+ if token.name == '<':
+ new_tokens, new_end = self._GetTemplateEnd(tokens, i+1)
+ AddType(self.ToType(new_tokens))
+ # If there is a comma after the template, we need to consume
+ # that here otherwise it becomes part of the name.
+ i = new_end
+ reference = pointer = array = False
+ elif token.name == ',':
+ AddType([])
+ reference = pointer = array = False
+ elif token.name == '*':
+ pointer = True
+ elif token.name == '&':
+ reference = True
+ elif token.name == '[':
+ pointer = True
+ elif token.name == ']':
+ pass
+ else:
+ name_tokens.append(token)
+ i += 1
+
+ if name_tokens:
+ # No '<' in the tokens, just a simple name and no template.
+ AddType([])
+ return result
+
+ def DeclarationToParts(self, parts, needs_name_removed):
+ name = None
+ default = []
+ if needs_name_removed:
+ # Handle default (initial) values properly.
+ for i, t in enumerate(parts):
+ if t.name == '=':
+ default = parts[i+1:]
+ name = parts[i-1].name
+ if name == ']' and parts[i-2].name == '[':
+ name = parts[i-3].name
+ i -= 1
+ parts = parts[:i-1]
+ break
+ else:
+ if parts[-1].token_type == tokenize.NAME:
+ name = parts.pop().name
+ else:
+ # TODO(nnorwitz): this is a hack that happens for code like
+ # Register(Foo<T>); where it thinks this is a function call
+ # but it's actually a declaration.
+ name = '???'
+ modifiers = []
+ type_name = []
+ other_tokens = []
+ templated_types = []
+ i = 0
+ end = len(parts)
+ while i < end:
+ p = parts[i]
+ if keywords.IsKeyword(p.name):
+ modifiers.append(p.name)
+ elif p.name == '<':
+ templated_tokens, new_end = self._GetTemplateEnd(parts, i+1)
+ templated_types = self.ToType(templated_tokens)
+ i = new_end - 1
+ # Don't add a spurious :: to data members being initialized.
+ next_index = i + 1
+ if next_index < end and parts[next_index].name == '::':
+ i += 1
+ elif p.name in ('[', ']', '='):
+ # These are handled elsewhere.
+ other_tokens.append(p)
+ elif p.name not in ('*', '&', '>'):
+ # Ensure that names have a space between them.
+ if (type_name and type_name[-1].token_type == tokenize.NAME and
+ p.token_type == tokenize.NAME):
+ type_name.append(tokenize.Token(tokenize.SYNTAX, ' ', 0, 0))
+ type_name.append(p)
+ else:
+ other_tokens.append(p)
+ i += 1
+ type_name = ''.join([t.name for t in type_name])
+ return name, type_name, templated_types, modifiers, default, other_tokens
+
+ def ToParameters(self, tokens):
+ if not tokens:
+ return []
+
+ result = []
+ name = type_name = ''
+ type_modifiers = []
+ pointer = reference = array = False
+ first_token = None
+ default = []
+
+ def AddParameter(end):
+ if default:
+ del default[0] # Remove flag.
+ parts = self.DeclarationToParts(type_modifiers, True)
+ (name, type_name, templated_types, modifiers,
+ unused_default, unused_other_tokens) = parts
+ parameter_type = Type(first_token.start, first_token.end,
+ type_name, templated_types, modifiers,
+ reference, pointer, array)
+ p = Parameter(first_token.start, end, name,
+ parameter_type, default)
+ result.append(p)
+
+ template_count = 0
+ brace_count = 0
+ for s in tokens:
+ if not first_token:
+ first_token = s
+
+ # Check for braces before templates, as we can have unmatched '<>'
+ # inside default arguments.
+ if s.name == '{':
+ brace_count += 1
+ elif s.name == '}':
+ brace_count -= 1
+ if brace_count > 0:
+ type_modifiers.append(s)
+ continue
+
+ if s.name == '<':
+ template_count += 1
+ elif s.name == '>':
+ template_count -= 1
+ if template_count > 0:
+ type_modifiers.append(s)
+ continue
+
+ if s.name == ',':
+ AddParameter(s.start)
+ name = type_name = ''
+ type_modifiers = []
+ pointer = reference = array = False
+ first_token = None
+ default = []
+ elif s.name == '*':
+ pointer = True
+ elif s.name == '&':
+ reference = True
+ elif s.name == '[':
+ array = True
+ elif s.name == ']':
+ pass # Just don't add to type_modifiers.
+ elif s.name == '=':
+ # Got a default value. Add any value (None) as a flag.
+ default.append(None)
+ elif default:
+ default.append(s)
+ else:
+ type_modifiers.append(s)
+ AddParameter(tokens[-1].end)
+ return result
+
+ def CreateReturnType(self, return_type_seq):
+ if not return_type_seq:
+ return None
+ start = return_type_seq[0].start
+ end = return_type_seq[-1].end
+ _, name, templated_types, modifiers, default, other_tokens = \
+ self.DeclarationToParts(return_type_seq, False)
+ names = [n.name for n in other_tokens]
+ reference = '&' in names
+ pointer = '*' in names
+ array = '[' in names
+ return Type(start, end, name, templated_types, modifiers,
+ reference, pointer, array)
+
+ def GetTemplateIndices(self, names):
+ # names is a list of strings.
+ start = names.index('<')
+ end = len(names) - 1
+ while end > 0:
+ if names[end] == '>':
+ break
+ end -= 1
+ return start, end+1
+
+class AstBuilder(object):
+ def __init__(self, token_stream, filename, in_class='', visibility=None,
+ namespace_stack=[]):
+ self.tokens = token_stream
+ self.filename = filename
+ # TODO(nnorwitz): use a better data structure (deque) for the queue.
+ # Switching directions of the "queue" improved perf by about 25%.
+ # Using a deque should be even better since we access from both sides.
+ self.token_queue = []
+ self.namespace_stack = namespace_stack[:]
+ self.in_class = in_class
+ if in_class is None:
+ self.in_class_name_only = None
+ else:
+ self.in_class_name_only = in_class.split('::')[-1]
+ self.visibility = visibility
+ self.in_function = False
+ self.current_token = None
+ # Keep the state whether we are currently handling a typedef or not.
+ self._handling_typedef = False
+
+ self.converter = TypeConverter(self.namespace_stack)
+
+ def HandleError(self, msg, token):
+ printable_queue = list(reversed(self.token_queue[-20:]))
+ sys.stderr.write('Got %s in %s @ %s %s\n' %
+ (msg, self.filename, token, printable_queue))
+
+ def Generate(self):
+ while 1:
+ token = self._GetNextToken()
+ if not token:
+ break
+
+ # Get the next token.
+ self.current_token = token
+
+ # Dispatch on the next token type.
+ if token.token_type == _INTERNAL_TOKEN:
+ if token.name == _NAMESPACE_POP:
+ self.namespace_stack.pop()
+ continue
+
+ try:
+ result = self._GenerateOne(token)
+ if result is not None:
+ yield result
+ except:
+ self.HandleError('exception', token)
+ raise
+
+ def _CreateVariable(self, pos_token, name, type_name, type_modifiers,
+ ref_pointer_name_seq, templated_types, value=None):
+ reference = '&' in ref_pointer_name_seq
+ pointer = '*' in ref_pointer_name_seq
+ array = '[' in ref_pointer_name_seq
+ var_type = Type(pos_token.start, pos_token.end, type_name,
+ templated_types, type_modifiers,
+ reference, pointer, array)
+ return VariableDeclaration(pos_token.start, pos_token.end,
+ name, var_type, value, self.namespace_stack)
+
+ def _GenerateOne(self, token):
+ if token.token_type == tokenize.NAME:
+ if (keywords.IsKeyword(token.name) and
+ not keywords.IsBuiltinType(token.name)):
+ if token.name == 'enum':
+ # Pop the next token and only put it back if it's not
+ # 'class'. This allows us to support the two-token
+ # 'enum class' keyword as if it were simply 'enum'.
+ next = self._GetNextToken()
+ if next.name != 'class':
+ self._AddBackToken(next)
+
+ method = getattr(self, 'handle_' + token.name)
+ return method()
+ elif token.name == self.in_class_name_only:
+ # The token name is the same as the class, must be a ctor if
+ # there is a paren. Otherwise, it's the return type.
+ # Peek ahead to get the next token to figure out which.
+ next = self._GetNextToken()
+ self._AddBackToken(next)
+ if next.token_type == tokenize.SYNTAX and next.name == '(':
+ return self._GetMethod([token], FUNCTION_CTOR, None, True)
+ # Fall through--handle like any other method.
+
+ # Handle data or function declaration/definition.
+ syntax = tokenize.SYNTAX
+ temp_tokens, last_token = \
+ self._GetVarTokensUpToIgnoringTemplates(syntax,
+ '(', ';', '{', '[')
+ temp_tokens.insert(0, token)
+ if last_token.name == '(':
+ # If there is an assignment before the paren,
+ # this is an expression, not a method.
+ expr = bool([e for e in temp_tokens if e.name == '='])
+ if expr:
+ new_temp = self._GetTokensUpTo(tokenize.SYNTAX, ';')
+ temp_tokens.append(last_token)
+ temp_tokens.extend(new_temp)
+ last_token = tokenize.Token(tokenize.SYNTAX, ';', 0, 0)
+
+ if last_token.name == '[':
+ # Handle array, this isn't a method, unless it's an operator.
+ # TODO(nnorwitz): keep the size somewhere.
+ # unused_size = self._GetTokensUpTo(tokenize.SYNTAX, ']')
+ temp_tokens.append(last_token)
+ if temp_tokens[-2].name == 'operator':
+ temp_tokens.append(self._GetNextToken())
+ else:
+ temp_tokens2, last_token = \
+ self._GetVarTokensUpTo(tokenize.SYNTAX, ';')
+ temp_tokens.extend(temp_tokens2)
+
+ if last_token.name == ';':
+ # Handle data, this isn't a method.
+ parts = self.converter.DeclarationToParts(temp_tokens, True)
+ (name, type_name, templated_types, modifiers, default,
+ unused_other_tokens) = parts
+
+ t0 = temp_tokens[0]
+ names = [t.name for t in temp_tokens]
+ if templated_types:
+ start, end = self.converter.GetTemplateIndices(names)
+ names = names[:start] + names[end:]
+ default = ''.join([t.name for t in default])
+ return self._CreateVariable(t0, name, type_name, modifiers,
+ names, templated_types, default)
+ if last_token.name == '{':
+ self._AddBackTokens(temp_tokens[1:])
+ self._AddBackToken(last_token)
+ method_name = temp_tokens[0].name
+ method = getattr(self, 'handle_' + method_name, None)
+ if not method:
+ # Must be declaring a variable.
+ # TODO(nnorwitz): handle the declaration.
+ return None
+ return method()
+ return self._GetMethod(temp_tokens, 0, None, False)
+ elif token.token_type == tokenize.SYNTAX:
+ if token.name == '~' and self.in_class:
+ # Must be a dtor (probably not in method body).
+ token = self._GetNextToken()
+ # self.in_class can contain A::Name, but the dtor will only
+ # be Name. Make sure to compare against the right value.
+ if (token.token_type == tokenize.NAME and
+ token.name == self.in_class_name_only):
+ return self._GetMethod([token], FUNCTION_DTOR, None, True)
+ # TODO(nnorwitz): handle a lot more syntax.
+ elif token.token_type == tokenize.PREPROCESSOR:
+ # TODO(nnorwitz): handle more preprocessor directives.
+ # token starts with a #, so remove it and strip whitespace.
+ name = token.name[1:].lstrip()
+ if name.startswith('include'):
+ # Remove "include".
+ name = name[7:].strip()
+ assert name
+ # Handle #include \<newline> "header-on-second-line.h".
+ if name.startswith('\\'):
+ name = name[1:].strip()
+ assert name[0] in '<"', token
+ assert name[-1] in '>"', token
+ system = name[0] == '<'
+ filename = name[1:-1]
+ return Include(token.start, token.end, filename, system)
+ if name.startswith('define'):
+ # Remove "define".
+ name = name[6:].strip()
+ assert name
+ value = ''
+ for i, c in enumerate(name):
+ if c.isspace():
+ value = name[i:].lstrip()
+ name = name[:i]
+ break
+ return Define(token.start, token.end, name, value)
+ if name.startswith('if') and name[2:3].isspace():
+ condition = name[3:].strip()
+ if condition.startswith('0') or condition.startswith('(0)'):
+ self._SkipIf0Blocks()
+ return None
+
+ def _GetTokensUpTo(self, expected_token_type, expected_token):
+ return self._GetVarTokensUpTo(expected_token_type, expected_token)[0]
+
+ def _GetVarTokensUpTo(self, expected_token_type, *expected_tokens):
+ last_token = self._GetNextToken()
+ tokens = []
+ while (last_token.token_type != expected_token_type or
+ last_token.name not in expected_tokens):
+ tokens.append(last_token)
+ last_token = self._GetNextToken()
+ return tokens, last_token
+
+ # Same as _GetVarTokensUpTo, but skips over '<...>' which could contain an
+ # expected token.
+ def _GetVarTokensUpToIgnoringTemplates(self, expected_token_type,
+ *expected_tokens):
+ last_token = self._GetNextToken()
+ tokens = []
+ nesting = 0
+ while (nesting > 0 or
+ last_token.token_type != expected_token_type or
+ last_token.name not in expected_tokens):
+ tokens.append(last_token)
+ last_token = self._GetNextToken()
+ if last_token.name == '<':
+ nesting += 1
+ elif last_token.name == '>':
+ nesting -= 1
+ return tokens, last_token
+
+ # TODO(nnorwitz): remove _IgnoreUpTo() it shouldn't be necesary.
+ def _IgnoreUpTo(self, token_type, token):
+ unused_tokens = self._GetTokensUpTo(token_type, token)
+
+ def _SkipIf0Blocks(self):
+ count = 1
+ while 1:
+ token = self._GetNextToken()
+ if token.token_type != tokenize.PREPROCESSOR:
+ continue
+
+ name = token.name[1:].lstrip()
+ if name.startswith('endif'):
+ count -= 1
+ if count == 0:
+ break
+ elif name.startswith('if'):
+ count += 1
+
+ def _GetMatchingChar(self, open_paren, close_paren, GetNextToken=None):
+ if GetNextToken is None:
+ GetNextToken = self._GetNextToken
+ # Assumes the current token is open_paren and we will consume
+ # and return up to the close_paren.
+ count = 1
+ token = GetNextToken()
+ while 1:
+ if token.token_type == tokenize.SYNTAX:
+ if token.name == open_paren:
+ count += 1
+ elif token.name == close_paren:
+ count -= 1
+ if count == 0:
+ break
+ yield token
+ token = GetNextToken()
+ yield token
+
+ def _GetParameters(self):
+ return self._GetMatchingChar('(', ')')
+
+ def GetScope(self):
+ return self._GetMatchingChar('{', '}')
+
+ def _GetNextToken(self):
+ if self.token_queue:
+ return self.token_queue.pop()
+ try:
+ return next(self.tokens)
+ except StopIteration:
+ return
+
+ def _AddBackToken(self, token):
+ if token.whence == tokenize.WHENCE_STREAM:
+ token.whence = tokenize.WHENCE_QUEUE
+ self.token_queue.insert(0, token)
+ else:
+ assert token.whence == tokenize.WHENCE_QUEUE, token
+ self.token_queue.append(token)
+
+ def _AddBackTokens(self, tokens):
+ if tokens:
+ if tokens[-1].whence == tokenize.WHENCE_STREAM:
+ for token in tokens:
+ token.whence = tokenize.WHENCE_QUEUE
+ self.token_queue[:0] = reversed(tokens)
+ else:
+ assert tokens[-1].whence == tokenize.WHENCE_QUEUE, tokens
+ self.token_queue.extend(reversed(tokens))
+
+ def GetName(self, seq=None):
+ """Returns ([tokens], next_token_info)."""
+ GetNextToken = self._GetNextToken
+ if seq is not None:
+ it = iter(seq)
+ GetNextToken = lambda: next(it)
+ next_token = GetNextToken()
+ tokens = []
+ last_token_was_name = False
+ while (next_token.token_type == tokenize.NAME or
+ (next_token.token_type == tokenize.SYNTAX and
+ next_token.name in ('::', '<'))):
+ # Two NAMEs in a row means the identifier should terminate.
+ # It's probably some sort of variable declaration.
+ if last_token_was_name and next_token.token_type == tokenize.NAME:
+ break
+ last_token_was_name = next_token.token_type == tokenize.NAME
+ tokens.append(next_token)
+ # Handle templated names.
+ if next_token.name == '<':
+ tokens.extend(self._GetMatchingChar('<', '>', GetNextToken))
+ last_token_was_name = True
+ next_token = GetNextToken()
+ return tokens, next_token
+
+ def GetMethod(self, modifiers, templated_types):
+ return_type_and_name = self._GetTokensUpTo(tokenize.SYNTAX, '(')
+ assert len(return_type_and_name) >= 1
+ return self._GetMethod(return_type_and_name, modifiers, templated_types,
+ False)
+
+ def _GetMethod(self, return_type_and_name, modifiers, templated_types,
+ get_paren):
+ template_portion = None
+ if get_paren:
+ token = self._GetNextToken()
+ assert token.token_type == tokenize.SYNTAX, token
+ if token.name == '<':
+ # Handle templatized dtors.
+ template_portion = [token]
+ template_portion.extend(self._GetMatchingChar('<', '>'))
+ token = self._GetNextToken()
+ assert token.token_type == tokenize.SYNTAX, token
+ assert token.name == '(', token
+
+ name = return_type_and_name.pop()
+ # Handle templatized ctors.
+ if name.name == '>':
+ index = 1
+ while return_type_and_name[index].name != '<':
+ index += 1
+ template_portion = return_type_and_name[index:] + [name]
+ del return_type_and_name[index:]
+ name = return_type_and_name.pop()
+ elif name.name == ']':
+ rt = return_type_and_name
+ assert rt[-1].name == '[', return_type_and_name
+ assert rt[-2].name == 'operator', return_type_and_name
+ name_seq = return_type_and_name[-2:]
+ del return_type_and_name[-2:]
+ name = tokenize.Token(tokenize.NAME, 'operator[]',
+ name_seq[0].start, name.end)
+ # Get the open paren so _GetParameters() below works.
+ unused_open_paren = self._GetNextToken()
+
+ # TODO(nnorwitz): store template_portion.
+ return_type = return_type_and_name
+ indices = name
+ if return_type:
+ indices = return_type[0]
+
+ # Force ctor for templatized ctors.
+ if name.name == self.in_class and not modifiers:
+ modifiers |= FUNCTION_CTOR
+ parameters = list(self._GetParameters())
+ del parameters[-1] # Remove trailing ')'.
+
+ # Handling operator() is especially weird.
+ if name.name == 'operator' and not parameters:
+ token = self._GetNextToken()
+ assert token.name == '(', token
+ parameters = list(self._GetParameters())
+ del parameters[-1] # Remove trailing ')'.
+
+ token = self._GetNextToken()
+ while token.token_type == tokenize.NAME:
+ modifier_token = token
+ token = self._GetNextToken()
+ if modifier_token.name == 'const':
+ modifiers |= FUNCTION_CONST
+ elif modifier_token.name == '__attribute__':
+ # TODO(nnorwitz): handle more __attribute__ details.
+ modifiers |= FUNCTION_ATTRIBUTE
+ assert token.name == '(', token
+ # Consume everything between the (parens).
+ unused_tokens = list(self._GetMatchingChar('(', ')'))
+ token = self._GetNextToken()
+ elif modifier_token.name == 'throw':
+ modifiers |= FUNCTION_THROW
+ assert token.name == '(', token
+ # Consume everything between the (parens).
+ unused_tokens = list(self._GetMatchingChar('(', ')'))
+ token = self._GetNextToken()
+ elif modifier_token.name == 'override':
+ modifiers |= FUNCTION_OVERRIDE
+ elif modifier_token.name == modifier_token.name.upper():
+ # HACK(nnorwitz): assume that all upper-case names
+ # are some macro we aren't expanding.
+ modifiers |= FUNCTION_UNKNOWN_ANNOTATION
+ else:
+ self.HandleError('unexpected token', modifier_token)
+
+ assert token.token_type == tokenize.SYNTAX, token
+ # Handle ctor initializers.
+ if token.name == ':':
+ # TODO(nnorwitz): anything else to handle for initializer list?
+ while token.name != ';' and token.name != '{':
+ token = self._GetNextToken()
+
+ # Handle pointer to functions that are really data but look
+ # like method declarations.
+ if token.name == '(':
+ if parameters[0].name == '*':
+ # name contains the return type.
+ name = parameters.pop()
+ # parameters contains the name of the data.
+ modifiers = [p.name for p in parameters]
+ # Already at the ( to open the parameter list.
+ function_parameters = list(self._GetMatchingChar('(', ')'))
+ del function_parameters[-1] # Remove trailing ')'.
+ # TODO(nnorwitz): store the function_parameters.
+ token = self._GetNextToken()
+ assert token.token_type == tokenize.SYNTAX, token
+ assert token.name == ';', token
+ return self._CreateVariable(indices, name.name, indices.name,
+ modifiers, '', None)
+ # At this point, we got something like:
+ # return_type (type::*name_)(params);
+ # This is a data member called name_ that is a function pointer.
+ # With this code: void (sq_type::*field_)(string&);
+ # We get: name=void return_type=[] parameters=sq_type ... field_
+ # TODO(nnorwitz): is return_type always empty?
+ # TODO(nnorwitz): this isn't even close to being correct.
+ # Just put in something so we don't crash and can move on.
+ real_name = parameters[-1]
+ modifiers = [p.name for p in self._GetParameters()]
+ del modifiers[-1] # Remove trailing ')'.
+ return self._CreateVariable(indices, real_name.name, indices.name,
+ modifiers, '', None)
+
+ if token.name == '{':
+ body = list(self.GetScope())
+ del body[-1] # Remove trailing '}'.
+ else:
+ body = None
+ if token.name == '=':
+ token = self._GetNextToken()
+
+ if token.name == 'default' or token.name == 'delete':
+ # Ignore explicitly defaulted and deleted special members
+ # in C++11.
+ token = self._GetNextToken()
+ else:
+ # Handle pure-virtual declarations.
+ assert token.token_type == tokenize.CONSTANT, token
+ assert token.name == '0', token
+ modifiers |= FUNCTION_PURE_VIRTUAL
+ token = self._GetNextToken()
+
+ if token.name == '[':
+ # TODO(nnorwitz): store tokens and improve parsing.
+ # template <typename T, size_t N> char (&ASH(T (&seq)[N]))[N];
+ tokens = list(self._GetMatchingChar('[', ']'))
+ token = self._GetNextToken()
+
+ assert token.name == ';', (token, return_type_and_name, parameters)
+
+ # Looks like we got a method, not a function.
+ if len(return_type) > 2 and return_type[-1].name == '::':
+ return_type, in_class = \
+ self._GetReturnTypeAndClassName(return_type)
+ return Method(indices.start, indices.end, name.name, in_class,
+ return_type, parameters, modifiers, templated_types,
+ body, self.namespace_stack)
+ return Function(indices.start, indices.end, name.name, return_type,
+ parameters, modifiers, templated_types, body,
+ self.namespace_stack)
+
+ def _GetReturnTypeAndClassName(self, token_seq):
+ # Splitting the return type from the class name in a method
+ # can be tricky. For example, Return::Type::Is::Hard::To::Find().
+ # Where is the return type and where is the class name?
+ # The heuristic used is to pull the last name as the class name.
+ # This includes all the templated type info.
+ # TODO(nnorwitz): if there is only One name like in the
+ # example above, punt and assume the last bit is the class name.
+
+ # Ignore a :: prefix, if exists so we can find the first real name.
+ i = 0
+ if token_seq[0].name == '::':
+ i = 1
+ # Ignore a :: suffix, if exists.
+ end = len(token_seq) - 1
+ if token_seq[end-1].name == '::':
+ end -= 1
+
+ # Make a copy of the sequence so we can append a sentinel
+ # value. This is required for GetName will has to have some
+ # terminating condition beyond the last name.
+ seq_copy = token_seq[i:end]
+ seq_copy.append(tokenize.Token(tokenize.SYNTAX, '', 0, 0))
+ names = []
+ while i < end:
+ # Iterate through the sequence parsing out each name.
+ new_name, next = self.GetName(seq_copy[i:])
+ assert new_name, 'Got empty new_name, next=%s' % next
+ # We got a pointer or ref. Add it to the name.
+ if next and next.token_type == tokenize.SYNTAX:
+ new_name.append(next)
+ names.append(new_name)
+ i += len(new_name)
+
+ # Now that we have the names, it's time to undo what we did.
+
+ # Remove the sentinel value.
+ names[-1].pop()
+ # Flatten the token sequence for the return type.
+ return_type = [e for seq in names[:-1] for e in seq]
+ # The class name is the last name.
+ class_name = names[-1]
+ return return_type, class_name
+
+ def handle_bool(self):
+ pass
+
+ def handle_char(self):
+ pass
+
+ def handle_int(self):
+ pass
+
+ def handle_long(self):
+ pass
+
+ def handle_short(self):
+ pass
+
+ def handle_double(self):
+ pass
+
+ def handle_float(self):
+ pass
+
+ def handle_void(self):
+ pass
+
+ def handle_wchar_t(self):
+ pass
+
+ def handle_unsigned(self):
+ pass
+
+ def handle_signed(self):
+ pass
+
+ def _GetNestedType(self, ctor):
+ name = None
+ name_tokens, token = self.GetName()
+ if name_tokens:
+ name = ''.join([t.name for t in name_tokens])
+
+ # Handle forward declarations.
+ if token.token_type == tokenize.SYNTAX and token.name == ';':
+ return ctor(token.start, token.end, name, None,
+ self.namespace_stack)
+
+ if token.token_type == tokenize.NAME and self._handling_typedef:
+ self._AddBackToken(token)
+ return ctor(token.start, token.end, name, None,
+ self.namespace_stack)
+
+ # Must be the type declaration.
+ fields = list(self._GetMatchingChar('{', '}'))
+ del fields[-1] # Remove trailing '}'.
+ if token.token_type == tokenize.SYNTAX and token.name == '{':
+ next = self._GetNextToken()
+ new_type = ctor(token.start, token.end, name, fields,
+ self.namespace_stack)
+ # A name means this is an anonymous type and the name
+ # is the variable declaration.
+ if next.token_type != tokenize.NAME:
+ return new_type
+ name = new_type
+ token = next
+
+ # Must be variable declaration using the type prefixed with keyword.
+ assert token.token_type == tokenize.NAME, token
+ return self._CreateVariable(token, token.name, name, [], '', None)
+
+ def handle_struct(self):
+ # Special case the handling typedef/aliasing of structs here.
+ # It would be a pain to handle in the class code.
+ name_tokens, var_token = self.GetName()
+ if name_tokens:
+ next_token = self._GetNextToken()
+ is_syntax = (var_token.token_type == tokenize.SYNTAX and
+ var_token.name[0] in '*&')
+ is_variable = (var_token.token_type == tokenize.NAME and
+ next_token.name == ';')
+ variable = var_token
+ if is_syntax and not is_variable:
+ variable = next_token
+ temp = self._GetNextToken()
+ if temp.token_type == tokenize.SYNTAX and temp.name == '(':
+ # Handle methods declared to return a struct.
+ t0 = name_tokens[0]
+ struct = tokenize.Token(tokenize.NAME, 'struct',
+ t0.start-7, t0.start-2)
+ type_and_name = [struct]
+ type_and_name.extend(name_tokens)
+ type_and_name.extend((var_token, next_token))
+ return self._GetMethod(type_and_name, 0, None, False)
+ assert temp.name == ';', (temp, name_tokens, var_token)
+ if is_syntax or (is_variable and not self._handling_typedef):
+ modifiers = ['struct']
+ type_name = ''.join([t.name for t in name_tokens])
+ position = name_tokens[0]
+ return self._CreateVariable(position, variable.name, type_name,
+ modifiers, var_token.name, None)
+ name_tokens.extend((var_token, next_token))
+ self._AddBackTokens(name_tokens)
+ else:
+ self._AddBackToken(var_token)
+ return self._GetClass(Struct, VISIBILITY_PUBLIC, None)
+
+ def handle_union(self):
+ return self._GetNestedType(Union)
+
+ def handle_enum(self):
+ return self._GetNestedType(Enum)
+
+ def handle_auto(self):
+ # TODO(nnorwitz): warn about using auto? Probably not since it
+ # will be reclaimed and useful for C++0x.
+ pass
+
+ def handle_register(self):
+ pass
+
+ def handle_const(self):
+ pass
+
+ def handle_inline(self):
+ pass
+
+ def handle_extern(self):
+ pass
+
+ def handle_static(self):
+ pass
+
+ def handle_virtual(self):
+ # What follows must be a method.
+ token = token2 = self._GetNextToken()
+ if token.name == 'inline':
+ # HACK(nnorwitz): handle inline dtors by ignoring 'inline'.
+ token2 = self._GetNextToken()
+ if token2.token_type == tokenize.SYNTAX and token2.name == '~':
+ return self.GetMethod(FUNCTION_VIRTUAL + FUNCTION_DTOR, None)
+ assert token.token_type == tokenize.NAME or token.name == '::', token
+ return_type_and_name, _ = self._GetVarTokensUpToIgnoringTemplates(
+ tokenize.SYNTAX, '(') # )
+ return_type_and_name.insert(0, token)
+ if token2 is not token:
+ return_type_and_name.insert(1, token2)
+ return self._GetMethod(return_type_and_name, FUNCTION_VIRTUAL,
+ None, False)
+
+ def handle_volatile(self):
+ pass
+
+ def handle_mutable(self):
+ pass
+
+ def handle_public(self):
+ assert self.in_class
+ self.visibility = VISIBILITY_PUBLIC
+
+ def handle_protected(self):
+ assert self.in_class
+ self.visibility = VISIBILITY_PROTECTED
+
+ def handle_private(self):
+ assert self.in_class
+ self.visibility = VISIBILITY_PRIVATE
+
+ def handle_friend(self):
+ tokens = self._GetTokensUpTo(tokenize.SYNTAX, ';')
+ assert tokens
+ t0 = tokens[0]
+ return Friend(t0.start, t0.end, tokens, self.namespace_stack)
+
+ def handle_static_cast(self):
+ pass
+
+ def handle_const_cast(self):
+ pass
+
+ def handle_dynamic_cast(self):
+ pass
+
+ def handle_reinterpret_cast(self):
+ pass
+
+ def handle_new(self):
+ pass
+
+ def handle_delete(self):
+ tokens = self._GetTokensUpTo(tokenize.SYNTAX, ';')
+ assert tokens
+ return Delete(tokens[0].start, tokens[0].end, tokens)
+
+ def handle_typedef(self):
+ token = self._GetNextToken()
+ if (token.token_type == tokenize.NAME and
+ keywords.IsKeyword(token.name)):
+ # Token must be struct/enum/union/class.
+ method = getattr(self, 'handle_' + token.name)
+ self._handling_typedef = True
+ tokens = [method()]
+ self._handling_typedef = False
+ else:
+ tokens = [token]
+
+ # Get the remainder of the typedef up to the semi-colon.
+ tokens.extend(self._GetTokensUpTo(tokenize.SYNTAX, ';'))
+
+ # TODO(nnorwitz): clean all this up.
+ assert tokens
+ name = tokens.pop()
+ indices = name
+ if tokens:
+ indices = tokens[0]
+ if not indices:
+ indices = token
+ if name.name == ')':
+ # HACK(nnorwitz): Handle pointers to functions "properly".
+ if (len(tokens) >= 4 and
+ tokens[1].name == '(' and tokens[2].name == '*'):
+ tokens.append(name)
+ name = tokens[3]
+ elif name.name == ']':
+ # HACK(nnorwitz): Handle arrays properly.
+ if len(tokens) >= 2:
+ tokens.append(name)
+ name = tokens[1]
+ new_type = tokens
+ if tokens and isinstance(tokens[0], tokenize.Token):
+ new_type = self.converter.ToType(tokens)[0]
+ return Typedef(indices.start, indices.end, name.name,
+ new_type, self.namespace_stack)
+
+ def handle_typeid(self):
+ pass # Not needed yet.
+
+ def handle_typename(self):
+ pass # Not needed yet.
+
+ def _GetTemplatedTypes(self):
+ result = {}
+ tokens = list(self._GetMatchingChar('<', '>'))
+ len_tokens = len(tokens) - 1 # Ignore trailing '>'.
+ i = 0
+ while i < len_tokens:
+ key = tokens[i].name
+ i += 1
+ if keywords.IsKeyword(key) or key == ',':
+ continue
+ type_name = default = None
+ if i < len_tokens:
+ i += 1
+ if tokens[i-1].name == '=':
+ assert i < len_tokens, '%s %s' % (i, tokens)
+ default, unused_next_token = self.GetName(tokens[i:])
+ i += len(default)
+ else:
+ if tokens[i-1].name != ',':
+ # We got something like: Type variable.
+ # Re-adjust the key (variable) and type_name (Type).
+ key = tokens[i-1].name
+ type_name = tokens[i-2]
+
+ result[key] = (type_name, default)
+ return result
+
+ def handle_template(self):
+ token = self._GetNextToken()
+ assert token.token_type == tokenize.SYNTAX, token
+ assert token.name == '<', token
+ templated_types = self._GetTemplatedTypes()
+ # TODO(nnorwitz): for now, just ignore the template params.
+ token = self._GetNextToken()
+ if token.token_type == tokenize.NAME:
+ if token.name == 'class':
+ return self._GetClass(Class, VISIBILITY_PRIVATE, templated_types)
+ elif token.name == 'struct':
+ return self._GetClass(Struct, VISIBILITY_PUBLIC, templated_types)
+ elif token.name == 'friend':
+ return self.handle_friend()
+ self._AddBackToken(token)
+ tokens, last = self._GetVarTokensUpTo(tokenize.SYNTAX, '(', ';')
+ tokens.append(last)
+ self._AddBackTokens(tokens)
+ if last.name == '(':
+ return self.GetMethod(FUNCTION_NONE, templated_types)
+ # Must be a variable definition.
+ return None
+
+ def handle_true(self):
+ pass # Nothing to do.
+
+ def handle_false(self):
+ pass # Nothing to do.
+
+ def handle_asm(self):
+ pass # Not needed yet.
+
+ def handle_class(self):
+ return self._GetClass(Class, VISIBILITY_PRIVATE, None)
+
+ def _GetBases(self):
+ # Get base classes.
+ bases = []
+ while 1:
+ token = self._GetNextToken()
+ assert token.token_type == tokenize.NAME, token
+ # TODO(nnorwitz): store kind of inheritance...maybe.
+ if token.name not in ('public', 'protected', 'private'):
+ # If inheritance type is not specified, it is private.
+ # Just put the token back so we can form a name.
+ # TODO(nnorwitz): it would be good to warn about this.
+ self._AddBackToken(token)
+ else:
+ # Check for virtual inheritance.
+ token = self._GetNextToken()
+ if token.name != 'virtual':
+ self._AddBackToken(token)
+ else:
+ # TODO(nnorwitz): store that we got virtual for this base.
+ pass
+ base, next_token = self.GetName()
+ bases_ast = self.converter.ToType(base)
+ assert len(bases_ast) == 1, bases_ast
+ bases.append(bases_ast[0])
+ assert next_token.token_type == tokenize.SYNTAX, next_token
+ if next_token.name == '{':
+ token = next_token
+ break
+ # Support multiple inheritance.
+ assert next_token.name == ',', next_token
+ return bases, token
+
+ def _GetClass(self, class_type, visibility, templated_types):
+ class_name = None
+ class_token = self._GetNextToken()
+ if class_token.token_type != tokenize.NAME:
+ assert class_token.token_type == tokenize.SYNTAX, class_token
+ token = class_token
+ else:
+ # Skip any macro (e.g. storage class specifiers) after the
+ # 'class' keyword.
+ next_token = self._GetNextToken()
+ if next_token.token_type == tokenize.NAME:
+ self._AddBackToken(next_token)
+ else:
+ self._AddBackTokens([class_token, next_token])
+ name_tokens, token = self.GetName()
+ class_name = ''.join([t.name for t in name_tokens])
+ bases = None
+ if token.token_type == tokenize.SYNTAX:
+ if token.name == ';':
+ # Forward declaration.
+ return class_type(class_token.start, class_token.end,
+ class_name, None, templated_types, None,
+ self.namespace_stack)
+ if token.name in '*&':
+ # Inline forward declaration. Could be method or data.
+ name_token = self._GetNextToken()
+ next_token = self._GetNextToken()
+ if next_token.name == ';':
+ # Handle data
+ modifiers = ['class']
+ return self._CreateVariable(class_token, name_token.name,
+ class_name,
+ modifiers, token.name, None)
+ else:
+ # Assume this is a method.
+ tokens = (class_token, token, name_token, next_token)
+ self._AddBackTokens(tokens)
+ return self.GetMethod(FUNCTION_NONE, None)
+ if token.name == ':':
+ bases, token = self._GetBases()
+
+ body = None
+ if token.token_type == tokenize.SYNTAX and token.name == '{':
+ assert token.token_type == tokenize.SYNTAX, token
+ assert token.name == '{', token
+
+ ast = AstBuilder(self.GetScope(), self.filename, class_name,
+ visibility, self.namespace_stack)
+ body = list(ast.Generate())
+
+ if not self._handling_typedef:
+ token = self._GetNextToken()
+ if token.token_type != tokenize.NAME:
+ assert token.token_type == tokenize.SYNTAX, token
+ assert token.name == ';', token
+ else:
+ new_class = class_type(class_token.start, class_token.end,
+ class_name, bases, None,
+ body, self.namespace_stack)
+
+ modifiers = []
+ return self._CreateVariable(class_token,
+ token.name, new_class,
+ modifiers, token.name, None)
+ else:
+ if not self._handling_typedef:
+ self.HandleError('non-typedef token', token)
+ self._AddBackToken(token)
+
+ return class_type(class_token.start, class_token.end, class_name,
+ bases, templated_types, body, self.namespace_stack)
+
+ def handle_namespace(self):
+ # Support anonymous namespaces.
+ name = None
+ name_tokens, token = self.GetName()
+ if name_tokens:
+ name = ''.join([t.name for t in name_tokens])
+ self.namespace_stack.append(name)
+ assert token.token_type == tokenize.SYNTAX, token
+ # Create an internal token that denotes when the namespace is complete.
+ internal_token = tokenize.Token(_INTERNAL_TOKEN, _NAMESPACE_POP,
+ None, None)
+ internal_token.whence = token.whence
+ if token.name == '=':
+ # TODO(nnorwitz): handle aliasing namespaces.
+ name, next_token = self.GetName()
+ assert next_token.name == ';', next_token
+ self._AddBackToken(internal_token)
+ else:
+ assert token.name == '{', token
+ tokens = list(self.GetScope())
+ # Replace the trailing } with the internal namespace pop token.
+ tokens[-1] = internal_token
+ # Handle namespace with nothing in it.
+ self._AddBackTokens(tokens)
+ return None
+
+ def handle_using(self):
+ tokens = self._GetTokensUpTo(tokenize.SYNTAX, ';')
+ assert tokens
+ return Using(tokens[0].start, tokens[0].end, tokens)
+
+ def handle_explicit(self):
+ assert self.in_class
+ # Nothing much to do.
+ # TODO(nnorwitz): maybe verify the method name == class name.
+ # This must be a ctor.
+ return self.GetMethod(FUNCTION_CTOR, None)
+
+ def handle_this(self):
+ pass # Nothing to do.
+
+ def handle_operator(self):
+ # Pull off the next token(s?) and make that part of the method name.
+ pass
+
+ def handle_sizeof(self):
+ pass
+
+ def handle_case(self):
+ pass
+
+ def handle_switch(self):
+ pass
+
+ def handle_default(self):
+ token = self._GetNextToken()
+ assert token.token_type == tokenize.SYNTAX
+ assert token.name == ':'
+
+ def handle_if(self):
+ pass
+
+ def handle_else(self):
+ pass
+
+ def handle_return(self):
+ tokens = self._GetTokensUpTo(tokenize.SYNTAX, ';')
+ if not tokens:
+ return Return(self.current_token.start, self.current_token.end, None)
+ return Return(tokens[0].start, tokens[0].end, tokens)
+
+ def handle_goto(self):
+ tokens = self._GetTokensUpTo(tokenize.SYNTAX, ';')
+ assert len(tokens) == 1, str(tokens)
+ return Goto(tokens[0].start, tokens[0].end, tokens[0].name)
+
+ def handle_try(self):
+ pass # Not needed yet.
+
+ def handle_catch(self):
+ pass # Not needed yet.
+
+ def handle_throw(self):
+ pass # Not needed yet.
+
+ def handle_while(self):
+ pass
+
+ def handle_do(self):
+ pass
+
+ def handle_for(self):
+ pass
+
+ def handle_break(self):
+ self._IgnoreUpTo(tokenize.SYNTAX, ';')
+
+ def handle_continue(self):
+ self._IgnoreUpTo(tokenize.SYNTAX, ';')
+
+
+def BuilderFromSource(source, filename):
+ """Utility method that returns an AstBuilder from source code.
+
+ Args:
+ source: 'C++ source code'
+ filename: 'file1'
+
+ Returns:
+ AstBuilder
+ """
+ return AstBuilder(tokenize.GetTokens(source), filename)
+
+
+def PrintIndentifiers(filename, should_print):
+ """Prints all identifiers for a C++ source file.
+
+ Args:
+ filename: 'file1'
+ should_print: predicate with signature: bool Function(token)
+ """
+ source = utils.ReadFile(filename, False)
+ if source is None:
+ sys.stderr.write('Unable to find: %s\n' % filename)
+ return
+
+ #print('Processing %s' % actual_filename)
+ builder = BuilderFromSource(source, filename)
+ try:
+ for node in builder.Generate():
+ if should_print(node):
+ print(node.name)
+ except KeyboardInterrupt:
+ return
+ except:
+ pass
+
+
+def PrintAllIndentifiers(filenames, should_print):
+ """Prints all identifiers for each C++ source file in filenames.
+
+ Args:
+ filenames: ['file1', 'file2', ...]
+ should_print: predicate with signature: bool Function(token)
+ """
+ for path in filenames:
+ PrintIndentifiers(path, should_print)
+
+
+def main(argv):
+ for filename in argv[1:]:
+ source = utils.ReadFile(filename)
+ if source is None:
+ continue
+
+ print('Processing %s' % filename)
+ builder = BuilderFromSource(source, filename)
+ try:
+ entire_ast = filter(None, builder.Generate())
+ except KeyboardInterrupt:
+ return
+ except:
+ # Already printed a warning, print the traceback and continue.
+ traceback.print_exc()
+ else:
+ if utils.DEBUG:
+ for ast in entire_ast:
+ print(ast)
+
+
+if __name__ == '__main__':
+ main(sys.argv)
diff --git a/src/googletest/googlemock/scripts/generator/cpp/gmock_class.py b/src/googletest/googlemock/scripts/generator/cpp/gmock_class.py
new file mode 100755
index 000000000..488cc1537
--- /dev/null
+++ b/src/googletest/googlemock/scripts/generator/cpp/gmock_class.py
@@ -0,0 +1,245 @@
+#!/usr/bin/env python
+#
+# Copyright 2008 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Generate Google Mock classes from base classes.
+
+This program will read in a C++ source file and output the Google Mock
+classes for the specified classes. If no class is specified, all
+classes in the source file are emitted.
+
+Usage:
+ gmock_class.py header-file.h [ClassName]...
+
+Output is sent to stdout.
+"""
+
+import os
+import re
+import sys
+
+from cpp import ast
+from cpp import utils
+
+# Preserve compatibility with Python 2.3.
+try:
+ _dummy = set
+except NameError:
+ import sets
+
+ set = sets.Set
+
+_VERSION = (1, 0, 1) # The version of this script.
+# How many spaces to indent. Can set me with the INDENT environment variable.
+_INDENT = 2
+
+
+def _RenderType(ast_type):
+ """Renders the potentially recursively templated type into a string.
+
+ Args:
+ ast_type: The AST of the type.
+
+ Returns:
+ Rendered string of the type.
+ """
+ # Add modifiers like 'const'.
+ modifiers = ''
+ if ast_type.modifiers:
+ modifiers = ' '.join(ast_type.modifiers) + ' '
+ return_type = modifiers + ast_type.name
+ if ast_type.templated_types:
+ # Collect template args.
+ template_args = []
+ for arg in ast_type.templated_types:
+ rendered_arg = _RenderType(arg)
+ template_args.append(rendered_arg)
+ return_type += '<' + ', '.join(template_args) + '>'
+ if ast_type.pointer:
+ return_type += '*'
+ if ast_type.reference:
+ return_type += '&'
+ return return_type
+
+
+def _GenerateArg(source):
+ """Strips out comments, default arguments, and redundant spaces from a single argument.
+
+ Args:
+ source: A string for a single argument.
+
+ Returns:
+ Rendered string of the argument.
+ """
+ # Remove end of line comments before eliminating newlines.
+ arg = re.sub(r'//.*', '', source)
+
+ # Remove c-style comments.
+ arg = re.sub(r'/\*.*\*/', '', arg)
+
+ # Remove default arguments.
+ arg = re.sub(r'=.*', '', arg)
+
+ # Collapse spaces and newlines into a single space.
+ arg = re.sub(r'\s+', ' ', arg)
+ return arg.strip()
+
+
+def _EscapeForMacro(s):
+ """Escapes a string for use as an argument to a C++ macro."""
+ paren_count = 0
+ for c in s:
+ if c == '(':
+ paren_count += 1
+ elif c == ')':
+ paren_count -= 1
+ elif c == ',' and paren_count == 0:
+ return '(' + s + ')'
+ return s
+
+
+def _GenerateMethods(output_lines, source, class_node):
+ function_type = (
+ ast.FUNCTION_VIRTUAL | ast.FUNCTION_PURE_VIRTUAL | ast.FUNCTION_OVERRIDE)
+ ctor_or_dtor = ast.FUNCTION_CTOR | ast.FUNCTION_DTOR
+ indent = ' ' * _INDENT
+
+ for node in class_node.body:
+ # We only care about virtual functions.
+ if (isinstance(node, ast.Function) and node.modifiers & function_type and
+ not node.modifiers & ctor_or_dtor):
+ # Pick out all the elements we need from the original function.
+ modifiers = 'override'
+ if node.modifiers & ast.FUNCTION_CONST:
+ modifiers = 'const, ' + modifiers
+
+ return_type = 'void'
+ if node.return_type:
+ return_type = _EscapeForMacro(_RenderType(node.return_type))
+
+ args = []
+ for p in node.parameters:
+ arg = _GenerateArg(source[p.start:p.end])
+ args.append(_EscapeForMacro(arg))
+
+ # Create the mock method definition.
+ output_lines.extend([
+ '%sMOCK_METHOD(%s, %s, (%s), (%s));' %
+ (indent, return_type, node.name, ', '.join(args), modifiers)
+ ])
+
+
+def _GenerateMocks(filename, source, ast_list, desired_class_names):
+ processed_class_names = set()
+ lines = []
+ for node in ast_list:
+ if (isinstance(node, ast.Class) and node.body and
+ # desired_class_names being None means that all classes are selected.
+ (not desired_class_names or node.name in desired_class_names)):
+ class_name = node.name
+ parent_name = class_name
+ processed_class_names.add(class_name)
+ class_node = node
+ # Add namespace before the class.
+ if class_node.namespace:
+ lines.extend(['namespace %s {' % n for n in class_node.namespace]) # }
+ lines.append('')
+
+ # Add template args for templated classes.
+ if class_node.templated_types:
+ # TODO(paulchang): The AST doesn't preserve template argument order,
+ # so we have to make up names here.
+ # TODO(paulchang): Handle non-type template arguments (e.g.
+ # template<typename T, int N>).
+ template_arg_count = len(class_node.templated_types.keys())
+ template_args = ['T%d' % n for n in range(template_arg_count)]
+ template_decls = ['typename ' + arg for arg in template_args]
+ lines.append('template <' + ', '.join(template_decls) + '>')
+ parent_name += '<' + ', '.join(template_args) + '>'
+
+ # Add the class prolog.
+ lines.append('class Mock%s : public %s {' # }
+ % (class_name, parent_name))
+ lines.append('%spublic:' % (' ' * (_INDENT // 2)))
+
+ # Add all the methods.
+ _GenerateMethods(lines, source, class_node)
+
+ # Close the class.
+ if lines:
+ # If there are no virtual methods, no need for a public label.
+ if len(lines) == 2:
+ del lines[-1]
+
+ # Only close the class if there really is a class.
+ lines.append('};')
+ lines.append('') # Add an extra newline.
+
+ # Close the namespace.
+ if class_node.namespace:
+ for i in range(len(class_node.namespace) - 1, -1, -1):
+ lines.append('} // namespace %s' % class_node.namespace[i])
+ lines.append('') # Add an extra newline.
+
+ if desired_class_names:
+ missing_class_name_list = list(desired_class_names - processed_class_names)
+ if missing_class_name_list:
+ missing_class_name_list.sort()
+ sys.stderr.write('Class(es) not found in %s: %s\n' %
+ (filename, ', '.join(missing_class_name_list)))
+ elif not processed_class_names:
+ sys.stderr.write('No class found in %s\n' % filename)
+
+ return lines
+
+
+def main(argv=sys.argv):
+ if len(argv) < 2:
+ sys.stderr.write('Google Mock Class Generator v%s\n\n' %
+ '.'.join(map(str, _VERSION)))
+ sys.stderr.write(__doc__)
+ return 1
+
+ global _INDENT
+ try:
+ _INDENT = int(os.environ['INDENT'])
+ except KeyError:
+ pass
+ except:
+ sys.stderr.write('Unable to use indent of %s\n' % os.environ.get('INDENT'))
+
+ filename = argv[1]
+ desired_class_names = None # None means all classes in the source file.
+ if len(argv) >= 3:
+ desired_class_names = set(argv[2:])
+ source = utils.ReadFile(filename)
+ if source is None:
+ return 1
+
+ builder = ast.BuilderFromSource(source, filename)
+ try:
+ entire_ast = filter(None, builder.Generate())
+ except KeyboardInterrupt:
+ return
+ except:
+ # An error message was already printed since we couldn't parse.
+ sys.exit(1)
+ else:
+ lines = _GenerateMocks(filename, source, entire_ast, desired_class_names)
+ sys.stdout.write('\n'.join(lines))
+
+
+if __name__ == '__main__':
+ main(sys.argv)
diff --git a/src/googletest/googlemock/scripts/generator/cpp/gmock_class_test.py b/src/googletest/googlemock/scripts/generator/cpp/gmock_class_test.py
new file mode 100755
index 000000000..527182cc3
--- /dev/null
+++ b/src/googletest/googlemock/scripts/generator/cpp/gmock_class_test.py
@@ -0,0 +1,552 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 Neal Norwitz All Rights Reserved.
+# Portions Copyright 2009 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tests for gmock.scripts.generator.cpp.gmock_class."""
+
+import os
+import sys
+import unittest
+
+# Allow the cpp imports below to work when run as a standalone script.
+sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+
+from cpp import ast
+from cpp import gmock_class
+
+
+class TestCase(unittest.TestCase):
+ """Helper class that adds assert methods."""
+
+ @staticmethod
+ def StripLeadingWhitespace(lines):
+ """Strip leading whitespace in each line in 'lines'."""
+ return '\n'.join([s.lstrip() for s in lines.split('\n')])
+
+ def assertEqualIgnoreLeadingWhitespace(self, expected_lines, lines):
+ """Specialized assert that ignores the indent level."""
+ self.assertEqual(expected_lines, self.StripLeadingWhitespace(lines))
+
+
+class GenerateMethodsTest(TestCase):
+
+ @staticmethod
+ def GenerateMethodSource(cpp_source):
+ """Convert C++ source to Google Mock output source lines."""
+ method_source_lines = []
+ # <test> is a pseudo-filename, it is not read or written.
+ builder = ast.BuilderFromSource(cpp_source, '<test>')
+ ast_list = list(builder.Generate())
+ gmock_class._GenerateMethods(method_source_lines, cpp_source, ast_list[0])
+ return '\n'.join(method_source_lines)
+
+ def testSimpleMethod(self):
+ source = """
+class Foo {
+ public:
+ virtual int Bar();
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(int, Bar, (), (override));',
+ self.GenerateMethodSource(source))
+
+ def testSimpleConstructorsAndDestructor(self):
+ source = """
+class Foo {
+ public:
+ Foo();
+ Foo(int x);
+ Foo(const Foo& f);
+ Foo(Foo&& f);
+ ~Foo();
+ virtual int Bar() = 0;
+};
+"""
+ # The constructors and destructor should be ignored.
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(int, Bar, (), (override));',
+ self.GenerateMethodSource(source))
+
+ def testVirtualDestructor(self):
+ source = """
+class Foo {
+ public:
+ virtual ~Foo();
+ virtual int Bar() = 0;
+};
+"""
+ # The destructor should be ignored.
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(int, Bar, (), (override));',
+ self.GenerateMethodSource(source))
+
+ def testExplicitlyDefaultedConstructorsAndDestructor(self):
+ source = """
+class Foo {
+ public:
+ Foo() = default;
+ Foo(const Foo& f) = default;
+ Foo(Foo&& f) = default;
+ ~Foo() = default;
+ virtual int Bar() = 0;
+};
+"""
+ # The constructors and destructor should be ignored.
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(int, Bar, (), (override));',
+ self.GenerateMethodSource(source))
+
+ def testExplicitlyDeletedConstructorsAndDestructor(self):
+ source = """
+class Foo {
+ public:
+ Foo() = delete;
+ Foo(const Foo& f) = delete;
+ Foo(Foo&& f) = delete;
+ ~Foo() = delete;
+ virtual int Bar() = 0;
+};
+"""
+ # The constructors and destructor should be ignored.
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(int, Bar, (), (override));',
+ self.GenerateMethodSource(source))
+
+ def testSimpleOverrideMethod(self):
+ source = """
+class Foo {
+ public:
+ int Bar() override;
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(int, Bar, (), (override));',
+ self.GenerateMethodSource(source))
+
+ def testSimpleConstMethod(self):
+ source = """
+class Foo {
+ public:
+ virtual void Bar(bool flag) const;
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(void, Bar, (bool flag), (const, override));',
+ self.GenerateMethodSource(source))
+
+ def testExplicitVoid(self):
+ source = """
+class Foo {
+ public:
+ virtual int Bar(void);
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(int, Bar, (void), (override));',
+ self.GenerateMethodSource(source))
+
+ def testStrangeNewlineInParameter(self):
+ source = """
+class Foo {
+ public:
+ virtual void Bar(int
+a) = 0;
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(void, Bar, (int a), (override));',
+ self.GenerateMethodSource(source))
+
+ def testDefaultParameters(self):
+ source = """
+class Foo {
+ public:
+ virtual void Bar(int a, char c = 'x') = 0;
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(void, Bar, (int a, char c), (override));',
+ self.GenerateMethodSource(source))
+
+ def testMultipleDefaultParameters(self):
+ source = """
+class Foo {
+ public:
+ virtual void Bar(
+ int a = 42,
+ char c = 'x',
+ const int* const p = nullptr,
+ const std::string& s = "42",
+ char tab[] = {'4','2'},
+ int const *& rp = aDefaultPointer) = 0;
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(void, Bar, '
+ '(int a, char c, const int* const p, const std::string& s, char tab[], int const *& rp), '
+ '(override));', self.GenerateMethodSource(source))
+
+ def testMultipleSingleLineDefaultParameters(self):
+ source = """
+class Foo {
+ public:
+ virtual void Bar(int a = 42, int b = 43, int c = 44) = 0;
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(void, Bar, (int a, int b, int c), (override));',
+ self.GenerateMethodSource(source))
+
+ def testConstDefaultParameter(self):
+ source = """
+class Test {
+ public:
+ virtual bool Bar(const int test_arg = 42) = 0;
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(bool, Bar, (const int test_arg), (override));',
+ self.GenerateMethodSource(source))
+
+ def testConstRefDefaultParameter(self):
+ source = """
+class Test {
+ public:
+ virtual bool Bar(const std::string& test_arg = "42" ) = 0;
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(bool, Bar, (const std::string& test_arg), (override));',
+ self.GenerateMethodSource(source))
+
+ def testRemovesCommentsWhenDefaultsArePresent(self):
+ source = """
+class Foo {
+ public:
+ virtual void Bar(int a = 42 /* a comment */,
+ char /* other comment */ c= 'x') = 0;
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(void, Bar, (int a, char c), (override));',
+ self.GenerateMethodSource(source))
+
+ def testDoubleSlashCommentsInParameterListAreRemoved(self):
+ source = """
+class Foo {
+ public:
+ virtual void Bar(int a, // inline comments should be elided.
+ int b // inline comments should be elided.
+ ) const = 0;
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(void, Bar, (int a, int b), (const, override));',
+ self.GenerateMethodSource(source))
+
+ def testCStyleCommentsInParameterListAreNotRemoved(self):
+ # NOTE(nnorwitz): I'm not sure if it's the best behavior to keep these
+ # comments. Also note that C style comments after the last parameter
+ # are still elided.
+ source = """
+class Foo {
+ public:
+ virtual const string& Bar(int /* keeper */, int b);
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(const string&, Bar, (int, int b), (override));',
+ self.GenerateMethodSource(source))
+
+ def testArgsOfTemplateTypes(self):
+ source = """
+class Foo {
+ public:
+ virtual int Bar(const vector<int>& v, map<int, string>* output);
+};"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(int, Bar, (const vector<int>& v, (map<int, string>* output)), (override));',
+ self.GenerateMethodSource(source))
+
+ def testReturnTypeWithOneTemplateArg(self):
+ source = """
+class Foo {
+ public:
+ virtual vector<int>* Bar(int n);
+};"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(vector<int>*, Bar, (int n), (override));',
+ self.GenerateMethodSource(source))
+
+ def testReturnTypeWithManyTemplateArgs(self):
+ source = """
+class Foo {
+ public:
+ virtual map<int, string> Bar();
+};"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD((map<int, string>), Bar, (), (override));',
+ self.GenerateMethodSource(source))
+
+ def testSimpleMethodInTemplatedClass(self):
+ source = """
+template<class T>
+class Foo {
+ public:
+ virtual int Bar();
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(int, Bar, (), (override));',
+ self.GenerateMethodSource(source))
+
+ def testPointerArgWithoutNames(self):
+ source = """
+class Foo {
+ virtual int Bar(C*);
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(int, Bar, (C*), (override));',
+ self.GenerateMethodSource(source))
+
+ def testReferenceArgWithoutNames(self):
+ source = """
+class Foo {
+ virtual int Bar(C&);
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(int, Bar, (C&), (override));',
+ self.GenerateMethodSource(source))
+
+ def testArrayArgWithoutNames(self):
+ source = """
+class Foo {
+ virtual int Bar(C[]);
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(
+ 'MOCK_METHOD(int, Bar, (C[]), (override));',
+ self.GenerateMethodSource(source))
+
+
+class GenerateMocksTest(TestCase):
+
+ @staticmethod
+ def GenerateMocks(cpp_source):
+ """Convert C++ source to complete Google Mock output source."""
+ # <test> is a pseudo-filename, it is not read or written.
+ filename = '<test>'
+ builder = ast.BuilderFromSource(cpp_source, filename)
+ ast_list = list(builder.Generate())
+ lines = gmock_class._GenerateMocks(filename, cpp_source, ast_list, None)
+ return '\n'.join(lines)
+
+ def testNamespaces(self):
+ source = """
+namespace Foo {
+namespace Bar { class Forward; }
+namespace Baz::Qux {
+
+class Test {
+ public:
+ virtual void Foo();
+};
+
+} // namespace Baz::Qux
+} // namespace Foo
+"""
+ expected = """\
+namespace Foo {
+namespace Baz::Qux {
+
+class MockTest : public Test {
+public:
+MOCK_METHOD(void, Foo, (), (override));
+};
+
+} // namespace Baz::Qux
+} // namespace Foo
+"""
+ self.assertEqualIgnoreLeadingWhitespace(expected,
+ self.GenerateMocks(source))
+
+ def testClassWithStorageSpecifierMacro(self):
+ source = """
+class STORAGE_SPECIFIER Test {
+ public:
+ virtual void Foo();
+};
+"""
+ expected = """\
+class MockTest : public Test {
+public:
+MOCK_METHOD(void, Foo, (), (override));
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(expected,
+ self.GenerateMocks(source))
+
+ def testTemplatedForwardDeclaration(self):
+ source = """
+template <class T> class Forward; // Forward declaration should be ignored.
+class Test {
+ public:
+ virtual void Foo();
+};
+"""
+ expected = """\
+class MockTest : public Test {
+public:
+MOCK_METHOD(void, Foo, (), (override));
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(expected,
+ self.GenerateMocks(source))
+
+ def testTemplatedClass(self):
+ source = """
+template <typename S, typename T>
+class Test {
+ public:
+ virtual void Foo();
+};
+"""
+ expected = """\
+template <typename T0, typename T1>
+class MockTest : public Test<T0, T1> {
+public:
+MOCK_METHOD(void, Foo, (), (override));
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(expected,
+ self.GenerateMocks(source))
+
+ def testTemplateInATemplateTypedef(self):
+ source = """
+class Test {
+ public:
+ typedef std::vector<std::list<int>> FooType;
+ virtual void Bar(const FooType& test_arg);
+};
+"""
+ expected = """\
+class MockTest : public Test {
+public:
+MOCK_METHOD(void, Bar, (const FooType& test_arg), (override));
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(expected,
+ self.GenerateMocks(source))
+
+ def testTemplateInATemplateTypedefWithComma(self):
+ source = """
+class Test {
+ public:
+ typedef std::function<void(
+ const vector<std::list<int>>&, int> FooType;
+ virtual void Bar(const FooType& test_arg);
+};
+"""
+ expected = """\
+class MockTest : public Test {
+public:
+MOCK_METHOD(void, Bar, (const FooType& test_arg), (override));
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(expected,
+ self.GenerateMocks(source))
+
+ def testParenthesizedCommaInArg(self):
+ source = """
+class Test {
+ public:
+ virtual void Bar(std::function<void(int, int)> f);
+};
+"""
+ expected = """\
+class MockTest : public Test {
+public:
+MOCK_METHOD(void, Bar, (std::function<void(int, int)> f), (override));
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(expected,
+ self.GenerateMocks(source))
+
+ def testEnumType(self):
+ source = """
+class Test {
+ public:
+ enum Bar {
+ BAZ, QUX, QUUX, QUUUX
+ };
+ virtual void Foo();
+};
+"""
+ expected = """\
+class MockTest : public Test {
+public:
+MOCK_METHOD(void, Foo, (), (override));
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(expected,
+ self.GenerateMocks(source))
+
+ def testEnumClassType(self):
+ source = """
+class Test {
+ public:
+ enum class Bar {
+ BAZ, QUX, QUUX, QUUUX
+ };
+ virtual void Foo();
+};
+"""
+ expected = """\
+class MockTest : public Test {
+public:
+MOCK_METHOD(void, Foo, (), (override));
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(expected,
+ self.GenerateMocks(source))
+
+ def testStdFunction(self):
+ source = """
+class Test {
+ public:
+ Test(std::function<int(std::string)> foo) : foo_(foo) {}
+
+ virtual std::function<int(std::string)> foo();
+
+ private:
+ std::function<int(std::string)> foo_;
+};
+"""
+ expected = """\
+class MockTest : public Test {
+public:
+MOCK_METHOD(std::function<int (std::string)>, foo, (), (override));
+};
+"""
+ self.assertEqualIgnoreLeadingWhitespace(expected,
+ self.GenerateMocks(source))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/googletest/googlemock/scripts/generator/cpp/keywords.py b/src/googletest/googlemock/scripts/generator/cpp/keywords.py
new file mode 100755
index 000000000..e4282714d
--- /dev/null
+++ b/src/googletest/googlemock/scripts/generator/cpp/keywords.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Neal Norwitz
+# Portions Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""C++ keywords and helper utilities for determining keywords."""
+
+try:
+ # Python 3.x
+ import builtins
+except ImportError:
+ # Python 2.x
+ import __builtin__ as builtins
+
+
+if not hasattr(builtins, 'set'):
+ # Nominal support for Python 2.3.
+ from sets import Set as set
+
+
+TYPES = set('bool char int long short double float void wchar_t unsigned signed'.split())
+TYPE_MODIFIERS = set('auto register const inline extern static virtual volatile mutable'.split())
+ACCESS = set('public protected private friend'.split())
+
+CASTS = set('static_cast const_cast dynamic_cast reinterpret_cast'.split())
+
+OTHERS = set('true false asm class namespace using explicit this operator sizeof'.split())
+OTHER_TYPES = set('new delete typedef struct union enum typeid typename template'.split())
+
+CONTROL = set('case switch default if else return goto'.split())
+EXCEPTION = set('try catch throw'.split())
+LOOP = set('while do for break continue'.split())
+
+ALL = TYPES | TYPE_MODIFIERS | ACCESS | CASTS | OTHERS | OTHER_TYPES | CONTROL | EXCEPTION | LOOP
+
+
+def IsKeyword(token):
+ return token in ALL
+
+def IsBuiltinType(token):
+ if token in ('virtual', 'inline'):
+ # These only apply to methods, they can't be types by themselves.
+ return False
+ return token in TYPES or token in TYPE_MODIFIERS
diff --git a/src/googletest/googlemock/scripts/generator/cpp/tokenize.py b/src/googletest/googlemock/scripts/generator/cpp/tokenize.py
new file mode 100755
index 000000000..a75edcb14
--- /dev/null
+++ b/src/googletest/googlemock/scripts/generator/cpp/tokenize.py
@@ -0,0 +1,284 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Neal Norwitz
+# Portions Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tokenize C++ source code."""
+
+try:
+ # Python 3.x
+ import builtins
+except ImportError:
+ # Python 2.x
+ import __builtin__ as builtins
+
+
+import sys
+
+from cpp import utils
+
+
+if not hasattr(builtins, 'set'):
+ # Nominal support for Python 2.3.
+ from sets import Set as set
+
+
+# Add $ as a valid identifier char since so much code uses it.
+_letters = 'abcdefghijklmnopqrstuvwxyz'
+VALID_IDENTIFIER_CHARS = set(_letters + _letters.upper() + '_0123456789$')
+HEX_DIGITS = set('0123456789abcdefABCDEF')
+INT_OR_FLOAT_DIGITS = set('01234567890eE-+')
+
+
+# C++0x string preffixes.
+_STR_PREFIXES = set(('R', 'u8', 'u8R', 'u', 'uR', 'U', 'UR', 'L', 'LR'))
+
+
+# Token types.
+UNKNOWN = 'UNKNOWN'
+SYNTAX = 'SYNTAX'
+CONSTANT = 'CONSTANT'
+NAME = 'NAME'
+PREPROCESSOR = 'PREPROCESSOR'
+
+# Where the token originated from. This can be used for backtracking.
+# It is always set to WHENCE_STREAM in this code.
+WHENCE_STREAM, WHENCE_QUEUE = range(2)
+
+
+class Token(object):
+ """Data container to represent a C++ token.
+
+ Tokens can be identifiers, syntax char(s), constants, or
+ pre-processor directives.
+
+ start contains the index of the first char of the token in the source
+ end contains the index of the last char of the token in the source
+ """
+
+ def __init__(self, token_type, name, start, end):
+ self.token_type = token_type
+ self.name = name
+ self.start = start
+ self.end = end
+ self.whence = WHENCE_STREAM
+
+ def __str__(self):
+ if not utils.DEBUG:
+ return 'Token(%r)' % self.name
+ return 'Token(%r, %s, %s)' % (self.name, self.start, self.end)
+
+ __repr__ = __str__
+
+
+def _GetString(source, start, i):
+ i = source.find('"', i+1)
+ while source[i-1] == '\\':
+ # Count the trailing backslashes.
+ backslash_count = 1
+ j = i - 2
+ while source[j] == '\\':
+ backslash_count += 1
+ j -= 1
+ # When trailing backslashes are even, they escape each other.
+ if (backslash_count % 2) == 0:
+ break
+ i = source.find('"', i+1)
+ return i + 1
+
+
+def _GetChar(source, start, i):
+ # NOTE(nnorwitz): may not be quite correct, should be good enough.
+ i = source.find("'", i+1)
+ while source[i-1] == '\\':
+ # Need to special case '\\'.
+ if (i - 2) > start and source[i-2] == '\\':
+ break
+ i = source.find("'", i+1)
+ # Try to handle unterminated single quotes (in a #if 0 block).
+ if i < 0:
+ i = start
+ return i + 1
+
+
+def GetTokens(source):
+ """Returns a sequence of Tokens.
+
+ Args:
+ source: string of C++ source code.
+
+ Yields:
+ Token that represents the next token in the source.
+ """
+ # Cache various valid character sets for speed.
+ valid_identifier_chars = VALID_IDENTIFIER_CHARS
+ hex_digits = HEX_DIGITS
+ int_or_float_digits = INT_OR_FLOAT_DIGITS
+ int_or_float_digits2 = int_or_float_digits | set('.')
+
+ # Only ignore errors while in a #if 0 block.
+ ignore_errors = False
+ count_ifs = 0
+
+ i = 0
+ end = len(source)
+ while i < end:
+ # Skip whitespace.
+ while i < end and source[i].isspace():
+ i += 1
+ if i >= end:
+ return
+
+ token_type = UNKNOWN
+ start = i
+ c = source[i]
+ if c.isalpha() or c == '_': # Find a string token.
+ token_type = NAME
+ while source[i] in valid_identifier_chars:
+ i += 1
+ # String and character constants can look like a name if
+ # they are something like L"".
+ if (source[i] == "'" and (i - start) == 1 and
+ source[start:i] in 'uUL'):
+ # u, U, and L are valid C++0x character preffixes.
+ token_type = CONSTANT
+ i = _GetChar(source, start, i)
+ elif source[i] == "'" and source[start:i] in _STR_PREFIXES:
+ token_type = CONSTANT
+ i = _GetString(source, start, i)
+ elif c == '/' and source[i+1] == '/': # Find // comments.
+ i = source.find('\n', i)
+ if i == -1: # Handle EOF.
+ i = end
+ continue
+ elif c == '/' and source[i+1] == '*': # Find /* comments. */
+ i = source.find('*/', i) + 2
+ continue
+ elif c in ':+-<>&|*=': # : or :: (plus other chars).
+ token_type = SYNTAX
+ i += 1
+ new_ch = source[i]
+ if new_ch == c and c != '>': # Treat ">>" as two tokens.
+ i += 1
+ elif c == '-' and new_ch == '>':
+ i += 1
+ elif new_ch == '=':
+ i += 1
+ elif c in '()[]{}~!?^%;/.,': # Handle single char tokens.
+ token_type = SYNTAX
+ i += 1
+ if c == '.' and source[i].isdigit():
+ token_type = CONSTANT
+ i += 1
+ while source[i] in int_or_float_digits:
+ i += 1
+ # Handle float suffixes.
+ for suffix in ('l', 'f'):
+ if suffix == source[i:i+1].lower():
+ i += 1
+ break
+ elif c.isdigit(): # Find integer.
+ token_type = CONSTANT
+ if c == '0' and source[i+1] in 'xX':
+ # Handle hex digits.
+ i += 2
+ while source[i] in hex_digits:
+ i += 1
+ else:
+ while source[i] in int_or_float_digits2:
+ i += 1
+ # Handle integer (and float) suffixes.
+ for suffix in ('ull', 'll', 'ul', 'l', 'f', 'u'):
+ size = len(suffix)
+ if suffix == source[i:i+size].lower():
+ i += size
+ break
+ elif c == '"': # Find string.
+ token_type = CONSTANT
+ i = _GetString(source, start, i)
+ elif c == "'": # Find char.
+ token_type = CONSTANT
+ i = _GetChar(source, start, i)
+ elif c == '#': # Find pre-processor command.
+ token_type = PREPROCESSOR
+ got_if = source[i:i+3] == '#if' and source[i+3:i+4].isspace()
+ if got_if:
+ count_ifs += 1
+ elif source[i:i+6] == '#endif':
+ count_ifs -= 1
+ if count_ifs == 0:
+ ignore_errors = False
+
+ # TODO(nnorwitz): handle preprocessor statements (\ continuations).
+ while 1:
+ i1 = source.find('\n', i)
+ i2 = source.find('//', i)
+ i3 = source.find('/*', i)
+ i4 = source.find('"', i)
+ # NOTE(nnorwitz): doesn't handle comments in #define macros.
+ # Get the first important symbol (newline, comment, EOF/end).
+ i = min([x for x in (i1, i2, i3, i4, end) if x != -1])
+
+ # Handle #include "dir//foo.h" properly.
+ if source[i] == '"':
+ i = source.find('"', i+1) + 1
+ assert i > 0
+ continue
+ # Keep going if end of the line and the line ends with \.
+ if not (i == i1 and source[i-1] == '\\'):
+ if got_if:
+ condition = source[start+4:i].lstrip()
+ if (condition.startswith('0') or
+ condition.startswith('(0)')):
+ ignore_errors = True
+ break
+ i += 1
+ elif c == '\\': # Handle \ in code.
+ # This is different from the pre-processor \ handling.
+ i += 1
+ continue
+ elif ignore_errors:
+ # The tokenizer seems to be in pretty good shape. This
+ # raise is conditionally disabled so that bogus code
+ # in an #if 0 block can be handled. Since we will ignore
+ # it anyways, this is probably fine. So disable the
+ # exception and return the bogus char.
+ i += 1
+ else:
+ sys.stderr.write('Got invalid token in %s @ %d token:%s: %r\n' %
+ ('?', i, c, source[i-10:i+10]))
+ raise RuntimeError('unexpected token')
+
+ if i <= 0:
+ print('Invalid index, exiting now.')
+ return
+ yield Token(token_type, source[start:i], start, i)
+
+
+if __name__ == '__main__':
+ def main(argv):
+ """Driver mostly for testing purposes."""
+ for filename in argv[1:]:
+ source = utils.ReadFile(filename)
+ if source is None:
+ continue
+
+ for token in GetTokens(source):
+ print('%-12s: %s' % (token.token_type, token.name))
+ # print('\r%6.2f%%' % (100.0 * index / token.end),)
+ sys.stdout.write('\n')
+
+
+ main(sys.argv)
diff --git a/src/googletest/googlemock/scripts/generator/cpp/utils.py b/src/googletest/googlemock/scripts/generator/cpp/utils.py
new file mode 100755
index 000000000..6f5fc097b
--- /dev/null
+++ b/src/googletest/googlemock/scripts/generator/cpp/utils.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Neal Norwitz
+# Portions Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Generic utilities for C++ parsing."""
+
+import sys
+
+# Set to True to see the start/end token indices.
+DEBUG = True
+
+
+def ReadFile(filename, print_error=True):
+ """Returns the contents of a file."""
+ try:
+ fp = open(filename)
+ try:
+ return fp.read()
+ finally:
+ fp.close()
+ except IOError:
+ if print_error:
+ print('Error reading %s: %s' % (filename, sys.exc_info()[1]))
+ return None
diff --git a/src/googletest/googlemock/scripts/generator/gmock_gen.py b/src/googletest/googlemock/scripts/generator/gmock_gen.py
new file mode 100755
index 000000000..9d528a56d
--- /dev/null
+++ b/src/googletest/googlemock/scripts/generator/gmock_gen.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+#
+# Copyright 2008 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Driver for starting up Google Mock class generator."""
+
+
+import os
+import sys
+
+if __name__ == '__main__':
+ # Add the directory of this script to the path so we can import gmock_class.
+ sys.path.append(os.path.dirname(__file__))
+
+ from cpp import gmock_class
+ # Fix the docstring in case they require the usage.
+ gmock_class.__doc__ = gmock_class.__doc__.replace('gmock_class.py', __file__)
+ gmock_class.main()