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
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.