Simple C++ code transformations

Simple C++ code transformations#

Visiting the CppElement tree#

Let’s transform the following code into a tree of CppElement:

code = """
int AddNumbers(const MyModule_Class& c);
"""

import srcmlcpp

options = srcmlcpp.SrcmlcppOptions()
cpp_unit = srcmlcpp.code_to_cpp_unit(options, code)

We can then “visit” this tree, and for example log the type of all encountered elements:

def visitor_log_info(cpp_element: srcmlcpp.CppElement, event: srcmlcpp.CppElementsVisitorEvent, depth: int) -> None:
    if event == srcmlcpp.CppElementsVisitorEvent.OnElement:
        print("  " * depth + cpp_element.short_cpp_element_info())


cpp_unit.visit_cpp_breadth_first(visitor_log_info)
CppUnit
  CppEmptyLine
  CppFunctionDecl name=AddNumbers
    CppType name=int
    CppParameterList
      CppParameter
        CppDecl name=c
          CppType name=MyModule_Class
  CppEmptyLine

Transforming the CppElement tree#

Let’s suppose we want to apply a mass source code transformation where:

  • all functions names should be transformed to “snake_case” (with a warning on top)

  • all types whose name start with MyModule_ should be replaced by MyModule:: (i.e. we want to add a namespace)

In our example,

int AddNumbers(const MyModule_Class& c);

should be transformed to

// Was changed to snake_case!
int add_numbers(const MyModule::Class& c);

We can achieve this by defining my_refactor_visitor this way:

from srcmlcpp import CppFunctionDecl, CppType  # import the types we want to apply transformations to
from codemanip import code_utils  # for to_snake_case


def change_function_to_snake_case(cpp_function: CppFunctionDecl):
    """Change a function name to snake_case"""
    cpp_function.function_name = code_utils.to_snake_case(cpp_function.function_name)
    cpp_function.cpp_element_comments.comment_on_previous_lines += "Was changed to snake case!"


def make_my_module_namespace(cpp_type: CppType):
    """If a type starts with MyModule_, replace it by MyModule::"""

    def change_typename(typename: str):
        if typename.startswith("MyModule_"):
            return typename.replace("MyModule_", "MyModule::")
        else:
            return typename

    cpp_type.typenames = [change_typename(typename) for typename in cpp_type.typenames]


def my_refactor_visitor(cpp_element: srcmlcpp.CppElement, event: srcmlcpp.CppElementsVisitorEvent, depth: int) -> None:
    """Our visitor will apply the transformation"""
    if event == srcmlcpp.CppElementsVisitorEvent.OnElement:
        if isinstance(cpp_element, CppFunctionDecl):
            change_function_to_snake_case(cpp_element)
        elif isinstance(cpp_element, CppType):
            make_my_module_namespace(cpp_element)

Let’s run this visitor and see its output:

# Let's visit the code
cpp_unit.visit_cpp_breadth_first(my_refactor_visitor)
# And print the refactored code
from litgen.demo import litgen_demo

litgen_demo.show_cpp_code(cpp_unit.str_code())
//Was changed to snake case!
int add_numbers(const MyModule::Class & c);

Note: str_code_verbatim still retains the original source code

litgen_demo.show_cpp_code(cpp_unit.str_code_verbatim())
int AddNumbers(const MyModule_Class& c);