docs: def _CompileTemplate(
          template_str, builder, meta='{}', format_char='|', default_formatter='str',
          whitespace='smart'):
      """Compile the template string, calling methods on the 'program builder'.
  
    Args:
      template_str: The template string.  It should not have any compilation
          options in the header -- those are parsed by FromString/FromFile
  
      builder: The interface of _ProgramBuilder isn't fixed.  Use at your own
          risk.
  
      meta: The metacharacters to use, e.g. '{}', '[]'.
  
      default_formatter: The formatter to use for substitutions that are missing a
          formatter.  The 'str' formatter the "default default" -- it just tries
          to convert the context value to a string in some unspecified manner.
  
      whitespace: 'smart' or 'strip-line'.  In smart mode, if a directive is alone
          on a line, with only whitespace on either side, then the whitespace is
          removed.  In 'strip-line' mode, every line is stripped of its
          leading and trailing whitespace.
  
    Returns:
      The compiled program (obtained from the builder)
  
    Raises:
      The various subclasses of CompilationError.  For example, if
      default_formatter=None, and a variable is missing a formatter, then
      MissingFormatter is raised.
  
    This function is public so it can be used by other tools, e.g. a syntax
    checking tool run before submitting a template to source control.
    """
      meta_left, meta_right = SplitMeta(meta)
      
      # : is meant to look like Python 3000 formatting {foo:.3f}.  According to
      # PEP 3101, that's also what .NET uses.
      # | is more readable, but, more importantly, reminiscent of pipes, which is
      # useful for multiple formatters, e.g. {name|js-string|html}
      if format_char not in (':', '|'):
          raise ConfigurationError(
              'Only format characters : and | are accepted (got %r)' % format_char)
      
      if whitespace not in ('smart', 'strip-line'):
          raise ConfigurationError('Invalid whitespace mode %r' % whitespace)
      
      # If we go to -1, then we got too many {end}.  If end at 1, then we're missing
      # an {end}.
      balance_counter = 0
      comment_counter = 0  # ditto for ##BEGIN/##END
      
      has_defines = False
      
      for token_type, token in _Tokenize(template_str, meta_left, meta_right,
                                         whitespace):
          if token_type == COMMENT_BEGIN_TOKEN:
              comment_counter += 1
              continue
          if token_type == COMMENT_END_TOKEN:
              comment_counter -= 1
              if comment_counter < 0:
                  raise CompilationError('Got too many ##END markers')
              continue
          # Don't process any tokens
          if comment_counter > 0:
              continue
          
          if token_type in (LITERAL_TOKEN, META_LITERAL_TOKEN):
              if token:
                  builder.Append(token)
              continue
          
          if token_type in (SECTION_TOKEN, REPEATED_SECTION_TOKEN, DEF_TOKEN):
              parts = [p.strip() for p in token.split(format_char)]
              if len(parts) == 1:
                  name = parts[0]
                  formatters = []
              else:
                  name = parts[0]
                  formatters = parts[1:]
              builder.NewSection(token_type, name, formatters)
              balance_counter += 1
              if token_type == DEF_TOKEN:
                  has_defines = True
              continue
          
          if token_type == PREDICATE_TOKEN:
              # {.attr?} lookups
              builder.NewPredicateSection(token, test_attr=True)
              balance_counter += 1
              continue
          
          if token_type == IF_TOKEN:
              builder.NewPredicateSection(token, test_attr=False)
              balance_counter += 1
              continue
          
          if token_type == OR_TOKEN:
              builder.NewOrClause(token)
              continue
          
          if token_type == ALTERNATES_TOKEN:
              builder.AlternatesWith()
              continue
          
          if token_type == END_TOKEN:
              balance_counter -= 1
              if balance_counter < 0:
                  # TODO: Show some context for errors
                  raise TemplateSyntaxError(
                      'Got too many %send%s statements.  You may have mistyped an '
                      "earlier 'section' or 'repeated section' directive."
                      % (meta_left, meta_right))
              builder.EndSection()
              continue
          
          if token_type == SUBST_TOKEN:
              parts = [p.strip() for p in token.split(format_char)]
              if len(parts) == 1:
                  if default_formatter is None:
                      raise MissingFormatter('This template requires explicit formatters.')
                  # If no formatter is specified, the default is the 'str' formatter,
                  # which the user can define however they desire.
                  name = token
                  formatters = [default_formatter]
              else:
                  name = parts[0]
                  formatters = parts[1:]
              
              builder.AppendSubstitution(name, formatters)
              continue
          
          if token_type == SUBST_TEMPLATE_TOKEN:
              # no formatters
              builder.AppendTemplateSubstitution(token)
              continue
      
      if balance_counter != 0:
          raise TemplateSyntaxError('Got too few %send%s statements' %
                                    (meta_left, meta_right))
      if comment_counter != 0:
          raise CompilationError('Got %d more {##BEGIN}s than {##END}s' % comment_counter)
      
      return builder.Root(), has_defines