code: def _modify_ast_subtree(
      tree,
      body_attr="body",
      is_root=False,
      file_ends_in_semicolon=False,
  ):
      """Parses magic commands and modifies the given AST (sub)tree."""
  
      body = getattr(tree, body_attr)
  
      for i, node in enumerate(body):
          node_type = type(node)
  
          # Recursively parses the content of the statements
          # `with`, `for` and `while`, as well as function definitions.
          # Also covers their async counterparts
          if (
              node_type is ast.FunctionDef
              or node_type is ast.With
              or node_type is ast.For
              or node_type is ast.While
              or node_type is ast.AsyncFunctionDef
              or node_type is ast.AsyncWith
              or node_type is ast.AsyncFor
          ):
              _modify_ast_subtree(node)
  
          # Recursively parses methods in a class.
          elif node_type is ast.ClassDef:
              for inner_node in node.body:
                  if type(inner_node) in {
                      ast.FunctionDef,
                      ast.AsyncFunctionDef,
                  }:
                      _modify_ast_subtree(inner_node)
  
          # Recursively parses the contents of try statements,
          # all their handlers (except and else) and the finally body
          elif node_type is ast.Try:
              for j, inner_node in enumerate(node.handlers):
                  node.handlers[j] = _modify_ast_subtree(inner_node)
              finally_node = _modify_ast_subtree(
                  node, body_attr="finalbody"
              )
              node.finalbody = finally_node.finalbody
              _modify_ast_subtree(node)
  
          # Recursively parses if blocks, as well as their else/elif blocks
          # (else/elif are both mapped to orelse)
          # it intentionally does not parse the test expression.
          elif node_type is ast.If:
              _modify_ast_subtree(node)
              _modify_ast_subtree(node, "orelse")
  
          # Convert standalone expression nodes to st.write
          elif node_type is ast.Expr:
              value = _get_st_write_from_expr(
                  node,
                  i,
                  parent_type=type(tree),
                  is_root=is_root,
                  is_last_expr=(i == len(body) - 1),
                  file_ends_in_semicolon=file_ends_in_semicolon,
              )
              if value is not None:
                  node.value = value
  
      if is_root:
          # Import Streamlit so we can use it in the new_value above.
          _insert_import_statement(tree)
  
      ast.fix_missing_locations(tree)
  
      return tree
  
file_path: streamlit/lib/streamlit/runtime/scriptrunner/magic.py
imports: import ast
  from typing_extensions import Final
  from streamlit import config
label_desc: Use image node_id 2 for calling a global function with example usage: _modify_ast_subtree(tree, body_attr, is_root, file_ends_in_semicolon) and returns: tree