1e35fdd936d133bf8a48de140a3c666897588a05shiqian#!/usr/bin/env python
2e35fdd936d133bf8a48de140a3c666897588a05shiqian#
3c2ad46a5df4414fc2b804c53525f4578f01a3dfezhanyong.wan# Copyright 2008 Google Inc.  All Rights Reserved.
4e35fdd936d133bf8a48de140a3c666897588a05shiqian#
5e35fdd936d133bf8a48de140a3c666897588a05shiqian# Licensed under the Apache License, Version 2.0 (the "License");
6e35fdd936d133bf8a48de140a3c666897588a05shiqian# you may not use this file except in compliance with the License.
7e35fdd936d133bf8a48de140a3c666897588a05shiqian# You may obtain a copy of the License at
8e35fdd936d133bf8a48de140a3c666897588a05shiqian#
9e35fdd936d133bf8a48de140a3c666897588a05shiqian#      http://www.apache.org/licenses/LICENSE-2.0
10e35fdd936d133bf8a48de140a3c666897588a05shiqian#
11e35fdd936d133bf8a48de140a3c666897588a05shiqian# Unless required by applicable law or agreed to in writing, software
12e35fdd936d133bf8a48de140a3c666897588a05shiqian# distributed under the License is distributed on an "AS IS" BASIS,
13e35fdd936d133bf8a48de140a3c666897588a05shiqian# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14e35fdd936d133bf8a48de140a3c666897588a05shiqian# See the License for the specific language governing permissions and
15e35fdd936d133bf8a48de140a3c666897588a05shiqian# limitations under the License.
16e35fdd936d133bf8a48de140a3c666897588a05shiqian
1784b8e4c65d0847ab4262bb70619182292482529azhanyong.wan"""Generate Google Mock classes from base classes.
18e35fdd936d133bf8a48de140a3c666897588a05shiqian
1984b8e4c65d0847ab4262bb70619182292482529azhanyong.wanThis program will read in a C++ source file and output the Google Mock
2084b8e4c65d0847ab4262bb70619182292482529azhanyong.wanclasses for the specified classes.  If no class is specified, all
2184b8e4c65d0847ab4262bb70619182292482529azhanyong.wanclasses in the source file are emitted.
22e35fdd936d133bf8a48de140a3c666897588a05shiqian
23e35fdd936d133bf8a48de140a3c666897588a05shiqianUsage:
2484b8e4c65d0847ab4262bb70619182292482529azhanyong.wan  gmock_class.py header-file.h [ClassName]...
25e35fdd936d133bf8a48de140a3c666897588a05shiqian
26e35fdd936d133bf8a48de140a3c666897588a05shiqianOutput is sent to stdout.
27e35fdd936d133bf8a48de140a3c666897588a05shiqian"""
28e35fdd936d133bf8a48de140a3c666897588a05shiqian
29e35fdd936d133bf8a48de140a3c666897588a05shiqian__author__ = 'nnorwitz@google.com (Neal Norwitz)'
30e35fdd936d133bf8a48de140a3c666897588a05shiqian
31e35fdd936d133bf8a48de140a3c666897588a05shiqian
32e35fdd936d133bf8a48de140a3c666897588a05shiqianimport os
33e35fdd936d133bf8a48de140a3c666897588a05shiqianimport re
34e35fdd936d133bf8a48de140a3c666897588a05shiqianimport sys
35e35fdd936d133bf8a48de140a3c666897588a05shiqian
36e35fdd936d133bf8a48de140a3c666897588a05shiqianfrom cpp import ast
37e35fdd936d133bf8a48de140a3c666897588a05shiqianfrom cpp import utils
38e35fdd936d133bf8a48de140a3c666897588a05shiqian
394b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan# Preserve compatibility with Python 2.3.
404b16e8ed2785136d863fb52961539c27c9716497zhanyong.wantry:
414b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan  _dummy = set
424b16e8ed2785136d863fb52961539c27c9716497zhanyong.wanexcept NameError:
434b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan  import sets
444b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan  set = sets.Set
454b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan
4684b8e4c65d0847ab4262bb70619182292482529azhanyong.wan_VERSION = (1, 0, 1)  # The version of this script.
4784b8e4c65d0847ab4262bb70619182292482529azhanyong.wan# How many spaces to indent.  Can set me with the INDENT environment variable.
48e35fdd936d133bf8a48de140a3c666897588a05shiqian_INDENT = 2
49e35fdd936d133bf8a48de140a3c666897588a05shiqian
50e35fdd936d133bf8a48de140a3c666897588a05shiqian
51e35fdd936d133bf8a48de140a3c666897588a05shiqiandef _GenerateMethods(output_lines, source, class_node):
52c26f969579d62444ae7d422b37e0037ceca97a7akosak  function_type = (ast.FUNCTION_VIRTUAL | ast.FUNCTION_PURE_VIRTUAL |
53c26f969579d62444ae7d422b37e0037ceca97a7akosak                   ast.FUNCTION_OVERRIDE)
54e35fdd936d133bf8a48de140a3c666897588a05shiqian  ctor_or_dtor = ast.FUNCTION_CTOR | ast.FUNCTION_DTOR
554b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan  indent = ' ' * _INDENT
56e35fdd936d133bf8a48de140a3c666897588a05shiqian
57e35fdd936d133bf8a48de140a3c666897588a05shiqian  for node in class_node.body:
58e35fdd936d133bf8a48de140a3c666897588a05shiqian    # We only care about virtual functions.
59e35fdd936d133bf8a48de140a3c666897588a05shiqian    if (isinstance(node, ast.Function) and
60e35fdd936d133bf8a48de140a3c666897588a05shiqian        node.modifiers & function_type and
61e35fdd936d133bf8a48de140a3c666897588a05shiqian        not node.modifiers & ctor_or_dtor):
62e35fdd936d133bf8a48de140a3c666897588a05shiqian      # Pick out all the elements we need from the original function.
63e35fdd936d133bf8a48de140a3c666897588a05shiqian      const = ''
64e35fdd936d133bf8a48de140a3c666897588a05shiqian      if node.modifiers & ast.FUNCTION_CONST:
65e35fdd936d133bf8a48de140a3c666897588a05shiqian        const = 'CONST_'
66e35fdd936d133bf8a48de140a3c666897588a05shiqian      return_type = 'void'
67e35fdd936d133bf8a48de140a3c666897588a05shiqian      if node.return_type:
6884b8e4c65d0847ab4262bb70619182292482529azhanyong.wan        # Add modifiers like 'const'.
69987a978c3c525cbc796824493436195872b89a0bnnorwitz        modifiers = ''
70987a978c3c525cbc796824493436195872b89a0bnnorwitz        if node.return_type.modifiers:
71987a978c3c525cbc796824493436195872b89a0bnnorwitz          modifiers = ' '.join(node.return_type.modifiers) + ' '
72987a978c3c525cbc796824493436195872b89a0bnnorwitz        return_type = modifiers + node.return_type.name
734b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan        template_args = [arg.name for arg in node.return_type.templated_types]
744b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan        if template_args:
754b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan          return_type += '<' + ', '.join(template_args) + '>'
764b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan          if len(template_args) > 1:
774b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan            for line in [
784b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan                '// The following line won\'t really compile, as the return',
794b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan                '// type has multiple template arguments.  To fix it, use a',
804b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan                '// typedef for the return type.']:
814b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan              output_lines.append(indent + line)
82e35fdd936d133bf8a48de140a3c666897588a05shiqian        if node.return_type.pointer:
83e35fdd936d133bf8a48de140a3c666897588a05shiqian          return_type += '*'
84e35fdd936d133bf8a48de140a3c666897588a05shiqian        if node.return_type.reference:
85e35fdd936d133bf8a48de140a3c666897588a05shiqian          return_type += '&'
86f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev        num_parameters = len(node.parameters)
87f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev        if len(node.parameters) == 1:
88f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev          first_param = node.parameters[0]
89f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev          if source[first_param.start:first_param.end].strip() == 'void':
90f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev            # We must treat T(void) as a function with no parameters.
91f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev            num_parameters = 0
9245fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan      tmpl = ''
9345fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan      if class_node.templated_types:
9445fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan        tmpl = '_T'
9545fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan      mock_method_macro = 'MOCK_%sMETHOD%d%s' % (const, num_parameters, tmpl)
9645fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan
97e35fdd936d133bf8a48de140a3c666897588a05shiqian      args = ''
98e35fdd936d133bf8a48de140a3c666897588a05shiqian      if node.parameters:
99f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev        # Due to the parser limitations, it is impossible to keep comments
100f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev        # while stripping the default parameters.  When defaults are
101f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev        # present, we choose to strip them and comments (and produce
102f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev        # compilable code).
103f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev        # TODO(nnorwitz@google.com): Investigate whether it is possible to
104f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev        # preserve parameter name when reconstructing parameter text from
105f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev        # the AST.
106f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev        if len([param for param in node.parameters if param.default]) > 0:
107f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev          args = ', '.join(param.type.name for param in node.parameters)
108f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev        else:
109f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev          # Get the full text of the parameters from the start
110f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev          # of the first parameter to the end of the last parameter.
111f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev          start = node.parameters[0].start
112f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev          end = node.parameters[-1].end
113f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev          # Remove // comments.
114f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev          args_strings = re.sub(r'//.*', '', source[start:end])
115f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev          # Condense multiple spaces and eliminate newlines putting the
116f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev          # parameters together on a single line.  Ensure there is a
117f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev          # space in an argument which is split by a newline without
118f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev          # intervening whitespace, e.g.: int\nBar
119f4eeaedb39b6935f6236fe55a52bd9af0b8390efvladlosev          args = re.sub('  +', ' ', args_strings.replace('\n', ' '))
120e35fdd936d133bf8a48de140a3c666897588a05shiqian
1214b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan      # Create the mock method definition.
1224b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan      output_lines.extend(['%s%s(%s,' % (indent, mock_method_macro, node.name),
1234b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan                           '%s%s(%s));' % (indent*3, return_type, args)])
124e35fdd936d133bf8a48de140a3c666897588a05shiqian
125e35fdd936d133bf8a48de140a3c666897588a05shiqian
12684b8e4c65d0847ab4262bb70619182292482529azhanyong.wandef _GenerateMocks(filename, source, ast_list, desired_class_names):
1274b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan  processed_class_names = set()
128e35fdd936d133bf8a48de140a3c666897588a05shiqian  lines = []
129e35fdd936d133bf8a48de140a3c666897588a05shiqian  for node in ast_list:
13084b8e4c65d0847ab4262bb70619182292482529azhanyong.wan    if (isinstance(node, ast.Class) and node.body and
13184b8e4c65d0847ab4262bb70619182292482529azhanyong.wan        # desired_class_names being None means that all classes are selected.
13284b8e4c65d0847ab4262bb70619182292482529azhanyong.wan        (not desired_class_names or node.name in desired_class_names)):
133ce60784fb51a5a0e28c14edd53bacbf0d2abb36bnnorwitz      class_name = node.name
13445fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan      parent_name = class_name
13584b8e4c65d0847ab4262bb70619182292482529azhanyong.wan      processed_class_names.add(class_name)
136e35fdd936d133bf8a48de140a3c666897588a05shiqian      class_node = node
137e35fdd936d133bf8a48de140a3c666897588a05shiqian      # Add namespace before the class.
138e35fdd936d133bf8a48de140a3c666897588a05shiqian      if class_node.namespace:
139e35fdd936d133bf8a48de140a3c666897588a05shiqian        lines.extend(['namespace %s {' % n for n in class_node.namespace])  # }
140e35fdd936d133bf8a48de140a3c666897588a05shiqian        lines.append('')
141e35fdd936d133bf8a48de140a3c666897588a05shiqian
14245fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan      # Add template args for templated classes.
14345fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan      if class_node.templated_types:
14445fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan        # TODO(paulchang): The AST doesn't preserve template argument order,
14545fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan        # so we have to make up names here.
14645fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan        # TODO(paulchang): Handle non-type template arguments (e.g.
14745fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan        # template<typename T, int N>).
14845fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan        template_arg_count = len(class_node.templated_types.keys())
14945fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan        template_args = ['T%d' % n for n in range(template_arg_count)]
15045fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan        template_decls = ['typename ' + arg for arg in template_args]
15145fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan        lines.append('template <' + ', '.join(template_decls) + '>')
15245fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan        parent_name += '<' + ', '.join(template_args) + '>'
15345fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan
154e35fdd936d133bf8a48de140a3c666897588a05shiqian      # Add the class prolog.
15545fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan      lines.append('class Mock%s : public %s {'  # }
15645fef502fac471efa4bf25b3d4104943463912ebzhanyong.wan                   % (class_name, parent_name))
157e35fdd936d133bf8a48de140a3c666897588a05shiqian      lines.append('%spublic:' % (' ' * (_INDENT // 2)))
158e35fdd936d133bf8a48de140a3c666897588a05shiqian
159e35fdd936d133bf8a48de140a3c666897588a05shiqian      # Add all the methods.
160e35fdd936d133bf8a48de140a3c666897588a05shiqian      _GenerateMethods(lines, source, class_node)
161e35fdd936d133bf8a48de140a3c666897588a05shiqian
162e35fdd936d133bf8a48de140a3c666897588a05shiqian      # Close the class.
163e35fdd936d133bf8a48de140a3c666897588a05shiqian      if lines:
164e35fdd936d133bf8a48de140a3c666897588a05shiqian        # If there are no virtual methods, no need for a public label.
165e35fdd936d133bf8a48de140a3c666897588a05shiqian        if len(lines) == 2:
166e35fdd936d133bf8a48de140a3c666897588a05shiqian          del lines[-1]
167e35fdd936d133bf8a48de140a3c666897588a05shiqian
168e35fdd936d133bf8a48de140a3c666897588a05shiqian        # Only close the class if there really is a class.
169e35fdd936d133bf8a48de140a3c666897588a05shiqian        lines.append('};')
170e35fdd936d133bf8a48de140a3c666897588a05shiqian        lines.append('')  # Add an extra newline.
171e35fdd936d133bf8a48de140a3c666897588a05shiqian
172e35fdd936d133bf8a48de140a3c666897588a05shiqian      # Close the namespace.
173e35fdd936d133bf8a48de140a3c666897588a05shiqian      if class_node.namespace:
174e35fdd936d133bf8a48de140a3c666897588a05shiqian        for i in range(len(class_node.namespace)-1, -1, -1):
175e35fdd936d133bf8a48de140a3c666897588a05shiqian          lines.append('}  // namespace %s' % class_node.namespace[i])
176e35fdd936d133bf8a48de140a3c666897588a05shiqian        lines.append('')  # Add an extra newline.
177e35fdd936d133bf8a48de140a3c666897588a05shiqian
17884b8e4c65d0847ab4262bb70619182292482529azhanyong.wan  if desired_class_names:
179d955e83bee3919b871616223b777bab2f04942d9zhanyong.wan    missing_class_name_list = list(desired_class_names - processed_class_names)
180d955e83bee3919b871616223b777bab2f04942d9zhanyong.wan    if missing_class_name_list:
181d955e83bee3919b871616223b777bab2f04942d9zhanyong.wan      missing_class_name_list.sort()
18284b8e4c65d0847ab4262bb70619182292482529azhanyong.wan      sys.stderr.write('Class(es) not found in %s: %s\n' %
183d955e83bee3919b871616223b777bab2f04942d9zhanyong.wan                       (filename, ', '.join(missing_class_name_list)))
18484b8e4c65d0847ab4262bb70619182292482529azhanyong.wan  elif not processed_class_names:
185c2ad46a5df4414fc2b804c53525f4578f01a3dfezhanyong.wan    sys.stderr.write('No class found in %s\n' % filename)
186c2ad46a5df4414fc2b804c53525f4578f01a3dfezhanyong.wan
187c2ad46a5df4414fc2b804c53525f4578f01a3dfezhanyong.wan  return lines
188e35fdd936d133bf8a48de140a3c666897588a05shiqian
189e35fdd936d133bf8a48de140a3c666897588a05shiqian
190e35fdd936d133bf8a48de140a3c666897588a05shiqiandef main(argv=sys.argv):
191ce60784fb51a5a0e28c14edd53bacbf0d2abb36bnnorwitz  if len(argv) < 2:
19284b8e4c65d0847ab4262bb70619182292482529azhanyong.wan    sys.stderr.write('Google Mock Class Generator v%s\n\n' %
19384b8e4c65d0847ab4262bb70619182292482529azhanyong.wan                     '.'.join(map(str, _VERSION)))
19484b8e4c65d0847ab4262bb70619182292482529azhanyong.wan    sys.stderr.write(__doc__)
195e35fdd936d133bf8a48de140a3c666897588a05shiqian    return 1
196e35fdd936d133bf8a48de140a3c666897588a05shiqian
197e35fdd936d133bf8a48de140a3c666897588a05shiqian  global _INDENT
198e35fdd936d133bf8a48de140a3c666897588a05shiqian  try:
199e35fdd936d133bf8a48de140a3c666897588a05shiqian    _INDENT = int(os.environ['INDENT'])
200e35fdd936d133bf8a48de140a3c666897588a05shiqian  except KeyError:
201e35fdd936d133bf8a48de140a3c666897588a05shiqian    pass
202e35fdd936d133bf8a48de140a3c666897588a05shiqian  except:
203e35fdd936d133bf8a48de140a3c666897588a05shiqian    sys.stderr.write('Unable to use indent of %s\n' % os.environ.get('INDENT'))
204e35fdd936d133bf8a48de140a3c666897588a05shiqian
205ce60784fb51a5a0e28c14edd53bacbf0d2abb36bnnorwitz  filename = argv[1]
20684b8e4c65d0847ab4262bb70619182292482529azhanyong.wan  desired_class_names = None  # None means all classes in the source file.
207ce60784fb51a5a0e28c14edd53bacbf0d2abb36bnnorwitz  if len(argv) >= 3:
2084b16e8ed2785136d863fb52961539c27c9716497zhanyong.wan    desired_class_names = set(argv[2:])
209e35fdd936d133bf8a48de140a3c666897588a05shiqian  source = utils.ReadFile(filename)
210e35fdd936d133bf8a48de140a3c666897588a05shiqian  if source is None:
211e35fdd936d133bf8a48de140a3c666897588a05shiqian    return 1
212e35fdd936d133bf8a48de140a3c666897588a05shiqian
213e35fdd936d133bf8a48de140a3c666897588a05shiqian  builder = ast.BuilderFromSource(source, filename)
214e35fdd936d133bf8a48de140a3c666897588a05shiqian  try:
215e35fdd936d133bf8a48de140a3c666897588a05shiqian    entire_ast = filter(None, builder.Generate())
216e35fdd936d133bf8a48de140a3c666897588a05shiqian  except KeyboardInterrupt:
217e35fdd936d133bf8a48de140a3c666897588a05shiqian    return
218e35fdd936d133bf8a48de140a3c666897588a05shiqian  except:
219e35fdd936d133bf8a48de140a3c666897588a05shiqian    # An error message was already printed since we couldn't parse.
220055b6b17d2354691af4b20f035f36c134fba2ac9kosak    sys.exit(1)
221e35fdd936d133bf8a48de140a3c666897588a05shiqian  else:
222c2ad46a5df4414fc2b804c53525f4578f01a3dfezhanyong.wan    lines = _GenerateMocks(filename, source, entire_ast, desired_class_names)
223c2ad46a5df4414fc2b804c53525f4578f01a3dfezhanyong.wan    sys.stdout.write('\n'.join(lines))
224e35fdd936d133bf8a48de140a3c666897588a05shiqian
225e35fdd936d133bf8a48de140a3c666897588a05shiqian
226e35fdd936d133bf8a48de140a3c666897588a05shiqianif __name__ == '__main__':
227e35fdd936d133bf8a48de140a3c666897588a05shiqian  main(sys.argv)
228