Ejemplo n.º 1
0
        private void Validate(
            ref LiteralToken literal,
            DefinitionInfo definition)
        {
            // Legal
            var literal2 = literal;

            if (definition.Get <ScalarDefinition>().Any(x => x.IsMatch(literal2)))
            {
                return;
            }

            // Not a string, convert
            if (literal.Type != TokenType.String)
            {
                var stringToken = new StringToken(literal.FileId, literal.Line, literal.Column, literal.ToString());

                // Legal
                if (definition.Get <StringDefinition>().Any(x => x.IsMatch(stringToken)))
                {
                    literal = stringToken;
                    return;
                }
            }

            // Illegal
            m_context.Error(literal, TemplateStrings.UnexpectedValue(literal));
        }
Ejemplo n.º 2
0
 private String GetErrorPrefix(
     Int32?fileId,
     Int32?line,
     Int32?column)
 {
     if (fileId != null)
     {
         var fileName = GetFileName(fileId.Value);
         if (line != null && column != null)
         {
             return($"{fileName} {TemplateStrings.LineColumn(line, column)}:");
         }
         else
         {
             return($"{fileName}:");
         }
     }
     else if (line != null && column != null)
     {
         return($"{TemplateStrings.LineColumn(line, column)}:");
     }
     else
     {
         return(String.Empty);
     }
 }
 internal void IncrementEvents()
 {
     if (m_events++ >= m_maxEvents)
     {
         throw new InvalidOperationException(TemplateStrings.MaxTemplateEventsExceeded());
     }
 }
 internal void IncrementDepth()
 {
     if (m_depth++ >= m_maxDepth)
     {
         throw new InvalidOperationException(TemplateStrings.MaxObjectDepthExceeded());
     }
 }
Ejemplo n.º 5
0
        private static Boolean MatchesDirective(
            String trimmed,
            String directive,
            Int32 expectedParameters,
            out List <String> parameters,
            out Exception ex)
        {
            if (trimmed.StartsWith(directive, StringComparison.Ordinal) &&
                (trimmed.Length == directive.Length || Char.IsWhiteSpace(trimmed[directive.Length])))
            {
                parameters = new List <String>();
                var startIndex = directive.Length;
                var inString   = false;
                var parens     = 0;
                for (var i = startIndex; i < trimmed.Length; i++)
                {
                    var c = trimmed[i];
                    if (Char.IsWhiteSpace(c) && !inString && parens == 0)
                    {
                        if (startIndex < i)
                        {
                            parameters.Add(trimmed.Substring(startIndex, i - startIndex));
                        }

                        startIndex = i + 1;
                    }
                    else if (c == '\'')
                    {
                        inString = !inString;
                    }
                    else if (c == '(' && !inString)
                    {
                        parens++;
                    }
                    else if (c == ')' && !inString)
                    {
                        parens--;
                    }
                }

                if (startIndex < trimmed.Length)
                {
                    parameters.Add(trimmed.Substring(startIndex));
                }

                if (expectedParameters != parameters.Count)
                {
                    ex         = new ArgumentException(TemplateStrings.ExpectedNParametersFollowingDirective(expectedParameters, directive, parameters.Count));
                    parameters = null;
                    return(false);
                }

                ex = null;
                return(true);
            }

            ex         = null;
            parameters = null;
            return(false);
        }
Ejemplo n.º 6
0
        private void HandleMappingWithAllLooseProperties(
            DefinitionInfo mappingDefinition,
            DefinitionInfo keyDefinition,
            DefinitionInfo valueDefinition,
            MappingToken mapping)
        {
            TemplateToken nextValue;
            var           keys = new HashSet <String>(StringComparer.OrdinalIgnoreCase);

            while (m_objectReader.AllowLiteral(out LiteralToken rawLiteral))
            {
                var nextKeyScalar = ParseScalar(rawLiteral, mappingDefinition.AllowedContext);

                // Expression
                if (nextKeyScalar is ExpressionToken)
                {
                    // Legal
                    if (mappingDefinition.AllowedContext.Length > 0)
                    {
                        m_memory.AddBytes(nextKeyScalar);
                        nextValue = ReadValue(valueDefinition);
                        mapping.Add(nextKeyScalar, nextValue);
                    }
                    // Illegal
                    else
                    {
                        m_context.Error(nextKeyScalar, TemplateStrings.ExpressionNotAllowed());
                        SkipValue();
                    }

                    continue;
                }

                // Not a string, convert
                if (!(nextKeyScalar is StringToken nextKey))
                {
                    nextKey = new StringToken(nextKeyScalar.FileId, nextKeyScalar.Line, nextKeyScalar.Column, nextKeyScalar.ToString());
                }

                // Duplicate
                if (!keys.Add(nextKey.Value))
                {
                    m_context.Error(nextKey, TemplateStrings.ValueAlreadyDefined(nextKey.Value));
                    SkipValue();
                    continue;
                }

                // Validate
                Validate(nextKey, keyDefinition);
                m_memory.AddBytes(nextKey);

                // Add the pair
                nextValue = ReadValue(valueDefinition);
                mapping.Add(nextKey, nextValue);
            }

            ExpectMappingEnd();
        }
Ejemplo n.º 7
0
        private void SkipValue(Boolean error = false)
        {
            m_memory.IncrementEvents();

            // Scalar
            if (m_objectReader.AllowLiteral(out LiteralToken literal))
            {
                if (error)
                {
                    m_context.Error(literal, TemplateStrings.UnexpectedValue(literal));
                }

                return;
            }

            // Sequence
            if (m_objectReader.AllowSequenceStart(out SequenceToken sequence))
            {
                m_memory.IncrementDepth();

                if (error)
                {
                    m_context.Error(sequence, TemplateStrings.UnexpectedSequenceStart());
                }

                while (!m_objectReader.AllowSequenceEnd())
                {
                    SkipValue();
                }

                m_memory.DecrementDepth();
                return;
            }

            // Mapping
            if (m_objectReader.AllowMappingStart(out MappingToken mapping))
            {
                m_memory.IncrementDepth();

                if (error)
                {
                    m_context.Error(mapping, TemplateStrings.UnexpectedMappingStart());
                }

                while (!m_objectReader.AllowMappingEnd())
                {
                    SkipValue();
                    SkipValue();
                }

                m_memory.DecrementDepth();
                return;
            }

            // Unexpected
            throw new InvalidOperationException(TemplateStrings.ExpectedScalarSequenceOrMapping());
        }
        internal void AddBytes(Int32 bytes)
        {
            checked
            {
                m_currentBytes += bytes;
            }

            if (m_currentBytes > m_maxBytes)
            {
                throw new InvalidOperationException(TemplateStrings.MaxObjectSizeExceeded());
            }
        }
Ejemplo n.º 9
0
        private void HandleMappingWithAllLooseProperties(
            DefinitionInfo mappingDefinition,
            DefinitionInfo keyDefinition,
            DefinitionInfo valueDefinition,
            MappingToken mapping)
        {
            var keys = new HashSet <String>(StringComparer.OrdinalIgnoreCase);

            while (m_unraveler.AllowScalar(mappingDefinition.Expand, out ScalarToken nextKeyScalar))
            {
                // Expression
                if (nextKeyScalar is ExpressionToken)
                {
                    if (nextKeyScalar is BasicExpressionToken)
                    {
                        mapping.Add(nextKeyScalar, Evaluate(valueDefinition));
                    }
                    else
                    {
                        var anyDefinition = new DefinitionInfo(mappingDefinition, TemplateConstants.Any);
                        mapping.Add(nextKeyScalar, Evaluate(anyDefinition));
                    }

                    continue;
                }

                // Not a string
                if (!(nextKeyScalar is StringToken nextKey))
                {
                    nextKey = new StringToken(nextKeyScalar.FileId, nextKeyScalar.Line, nextKeyScalar.Column, nextKeyScalar.ToString());
                }

                // Duplicate
                if (!keys.Add(nextKey.Value))
                {
                    m_context.Error(nextKey, TemplateStrings.ValueAlreadyDefined(nextKey.Value));
                    m_unraveler.SkipMappingValue();
                    continue;
                }

                // Validate
                Validate(nextKey, keyDefinition);

                // Add the pair
                var nextValue = Evaluate(valueDefinition);
                mapping.Add(nextKey, nextValue);
            }

            m_unraveler.ReadMappingEnd();
        }
Ejemplo n.º 10
0
        private void Validate(
            ref ScalarToken scalar,
            DefinitionInfo definition)
        {
            switch (scalar.Type)
            {
            case TokenType.Null:
            case TokenType.Boolean:
            case TokenType.Number:
            case TokenType.String:
                var literal = scalar as LiteralToken;

                // Legal
                if (definition.Get <ScalarDefinition>().Any(x => x.IsMatch(literal)))
                {
                    return;
                }

                // Not a string, convert
                if (literal.Type != TokenType.String)
                {
                    literal = new StringToken(literal.FileId, literal.Line, literal.Column, literal.ToString());

                    // Legal
                    if (definition.Get <StringDefinition>().Any(x => x.IsMatch(literal)))
                    {
                        scalar = literal;
                        return;
                    }
                }

                // Illegal
                m_context.Error(literal, TemplateStrings.UnexpectedValue(literal));
                break;

            case TokenType.BasicExpression:

                // Illegal
                if (definition.AllowedContext.Length == 0)
                {
                    m_context.Error(scalar, TemplateStrings.ExpressionNotAllowed());
                }

                break;

            default:
                m_context.Error(scalar, TemplateStrings.UnexpectedValue(scalar));
                break;
            }
        }
Ejemplo n.º 11
0
        private ExpressionToken ParseExpression(
            Int32?line,
            Int32?column,
            String value,
            String[] allowedContext,
            out Exception ex)
        {
            var trimmed = value.Trim();

            // Check if the value is empty
            if (String.IsNullOrEmpty(trimmed))
            {
                ex = new ArgumentException(TemplateStrings.ExpectedExpression());
                return(null);
            }

            // Try to find a matching directive
            List <String> parameters;

            if (MatchesDirective(trimmed, TemplateConstants.InsertDirective, 0, out parameters, out ex))
            {
                return(new InsertExpressionToken(m_fileId, line, column));
            }
            else if (ex != null)
            {
                return(null);
            }

            // Check if the value is an expression
            if (!ExpressionToken.IsValidExpression(trimmed, allowedContext, out ex))
            {
                return(null);
            }

            // Return the expression
            return(new BasicExpressionToken(m_fileId, line, column, trimmed));
        }
Ejemplo n.º 12
0
        private TemplateToken Evaluate(DefinitionInfo definition)
        {
            // Scalar
            if (m_unraveler.AllowScalar(definition.Expand, out ScalarToken scalar))
            {
                if (scalar is LiteralToken literal)
                {
                    Validate(ref literal, definition);
                    return(literal);
                }
                else
                {
                    return(scalar);
                }
            }

            // Sequence start
            if (m_unraveler.AllowSequenceStart(definition.Expand, out SequenceToken sequence))
            {
                var sequenceDefinition = definition.Get <SequenceDefinition>().FirstOrDefault();

                // Legal
                if (sequenceDefinition != null)
                {
                    var itemDefinition = new DefinitionInfo(definition, sequenceDefinition.ItemType);

                    // Add each item
                    while (!m_unraveler.AllowSequenceEnd(definition.Expand))
                    {
                        var item = Evaluate(itemDefinition);
                        sequence.Add(item);
                    }
                }
                // Illegal
                else
                {
                    // Error
                    m_context.Error(sequence, TemplateStrings.UnexpectedSequenceStart());

                    // Skip each item
                    while (!m_unraveler.AllowSequenceEnd(expand: false))
                    {
                        m_unraveler.SkipSequenceItem();
                    }
                }

                return(sequence);
            }

            // Mapping
            if (m_unraveler.AllowMappingStart(definition.Expand, out MappingToken mapping))
            {
                var mappingDefinitions = definition.Get <MappingDefinition>().ToList();

                // Legal
                if (mappingDefinitions.Count > 0)
                {
                    if (mappingDefinitions.Count > 1 ||
                        m_schema.HasProperties(mappingDefinitions[0]) ||
                        String.IsNullOrEmpty(mappingDefinitions[0].LooseKeyType))
                    {
                        HandleMappingWithWellKnownProperties(definition, mappingDefinitions, mapping);
                    }
                    else
                    {
                        var keyDefinition   = new DefinitionInfo(definition, mappingDefinitions[0].LooseKeyType);
                        var valueDefinition = new DefinitionInfo(definition, mappingDefinitions[0].LooseValueType);
                        HandleMappingWithAllLooseProperties(definition, keyDefinition, valueDefinition, mapping);
                    }
                }
                // Illegal
                else
                {
                    m_context.Error(mapping, TemplateStrings.UnexpectedMappingStart());

                    while (!m_unraveler.AllowMappingEnd(expand: false))
                    {
                        m_unraveler.SkipMappingKey();
                        m_unraveler.SkipMappingValue();
                    }
                }

                return(mapping);
            }

            throw new ArgumentException(TemplateStrings.ExpectedScalarSequenceOrMapping());
        }
Ejemplo n.º 13
0
        private void HandleMappingWithWellKnownProperties(
            DefinitionInfo definition,
            List <MappingDefinition> mappingDefinitions,
            MappingToken mapping)
        {
            // Check if loose properties are allowed
            String         looseKeyType         = null;
            String         looseValueType       = null;
            DefinitionInfo?looseKeyDefinition   = null;
            DefinitionInfo?looseValueDefinition = null;

            if (!String.IsNullOrEmpty(mappingDefinitions[0].LooseKeyType))
            {
                looseKeyType   = mappingDefinitions[0].LooseKeyType;
                looseValueType = mappingDefinitions[0].LooseValueType;
            }

            var keys             = new HashSet <String>(StringComparer.OrdinalIgnoreCase);
            var hasExpressionKey = false;

            while (m_objectReader.AllowLiteral(out LiteralToken rawLiteral))
            {
                var nextKeyScalar = ParseScalar(rawLiteral, definition.AllowedContext);
                // Expression
                if (nextKeyScalar is ExpressionToken)
                {
                    hasExpressionKey = true;
                    // Legal
                    if (definition.AllowedContext.Length > 0)
                    {
                        m_memory.AddBytes(nextKeyScalar);
                        var anyDefinition = new DefinitionInfo(definition, TemplateConstants.Any);
                        mapping.Add(nextKeyScalar, ReadValue(anyDefinition));
                    }
                    // Illegal
                    else
                    {
                        m_context.Error(nextKeyScalar, TemplateStrings.ExpressionNotAllowed());
                        SkipValue();
                    }

                    continue;
                }

                // Not a string, convert
                if (!(nextKeyScalar is StringToken nextKey))
                {
                    nextKey = new StringToken(nextKeyScalar.FileId, nextKeyScalar.Line, nextKeyScalar.Column, nextKeyScalar.ToString());
                }

                // Duplicate
                if (!keys.Add(nextKey.Value))
                {
                    m_context.Error(nextKey, TemplateStrings.ValueAlreadyDefined(nextKey.Value));
                    SkipValue();
                    continue;
                }

                // Well known
                if (m_schema.TryMatchKey(mappingDefinitions, nextKey.Value, out String nextValueType))
                {
                    m_memory.AddBytes(nextKey);
                    var nextValueDefinition = new DefinitionInfo(definition, nextValueType);
                    var nextValue           = ReadValue(nextValueDefinition);
                    mapping.Add(nextKey, nextValue);
                    continue;
                }

                // Loose
                if (looseKeyType != null)
                {
                    if (looseKeyDefinition == null)
                    {
                        looseKeyDefinition   = new DefinitionInfo(definition, looseKeyType);
                        looseValueDefinition = new DefinitionInfo(definition, looseValueType);
                    }

                    Validate(nextKey, looseKeyDefinition.Value);
                    m_memory.AddBytes(nextKey);
                    var nextValue = ReadValue(looseValueDefinition.Value);
                    mapping.Add(nextKey, nextValue);
                    continue;
                }

                // Error
                m_context.Error(nextKey, TemplateStrings.UnexpectedValue(nextKey.Value));
                SkipValue();
            }

            // Only one
            if (mappingDefinitions.Count > 1)
            {
                var hitCount = new Dictionary <String, Int32>();
                foreach (MappingDefinition mapdef in mappingDefinitions)
                {
                    foreach (String key in mapdef.Properties.Keys)
                    {
                        if (!hitCount.TryGetValue(key, out Int32 value))
                        {
                            hitCount.Add(key, 1);
                        }
                        else
                        {
                            hitCount[key] = value + 1;
                        }
                    }
                }

                List <String> nonDuplicates = new List <String>();
                foreach (String key in hitCount.Keys)
                {
                    if (hitCount[key] == 1)
                    {
                        nonDuplicates.Add(key);
                    }
                }
                nonDuplicates.Sort();

                String listToDeDuplicate = String.Join(", ", nonDuplicates);
                m_context.Error(mapping, TemplateStrings.UnableToDetermineOneOf(listToDeDuplicate));
            }
            else if (mappingDefinitions.Count == 1 && !hasExpressionKey)
            {
                foreach (var property in mappingDefinitions[0].Properties)
                {
                    if (property.Value.Required)
                    {
                        if (!keys.Contains(property.Key))
                        {
                            m_context.Error(mapping, $"Required property is missing: {property.Key}");
                        }
                    }
                }
            }
            ExpectMappingEnd();
        }
Ejemplo n.º 14
0
        private void HandleMappingWithWellKnownProperties(
            DefinitionInfo definition,
            List <MappingDefinition> mappingDefinitions,
            MappingToken mapping)
        {
            // Check if loose properties are allowed
            String         looseKeyType         = null;
            String         looseValueType       = null;
            DefinitionInfo?looseKeyDefinition   = null;
            DefinitionInfo?looseValueDefinition = null;

            if (!String.IsNullOrEmpty(mappingDefinitions[0].LooseKeyType))
            {
                looseKeyType   = mappingDefinitions[0].LooseKeyType;
                looseValueType = mappingDefinitions[0].LooseValueType;
            }

            var keys = new HashSet <String>(StringComparer.OrdinalIgnoreCase);

            while (m_unraveler.AllowScalar(definition.Expand, out ScalarToken nextKeyScalar))
            {
                // Expression
                if (nextKeyScalar is ExpressionToken)
                {
                    var anyDefinition = new DefinitionInfo(definition, TemplateConstants.Any);
                    mapping.Add(nextKeyScalar, Evaluate(anyDefinition));
                    continue;
                }

                // Not a string, convert
                if (!(nextKeyScalar is StringToken nextKey))
                {
                    nextKey = new StringToken(nextKeyScalar.FileId, nextKeyScalar.Line, nextKeyScalar.Column, nextKeyScalar.ToString());
                }

                // Duplicate
                if (!keys.Add(nextKey.Value))
                {
                    m_context.Error(nextKey, TemplateStrings.ValueAlreadyDefined(nextKey.Value));
                    m_unraveler.SkipMappingValue();
                    continue;
                }

                // Well known
                if (m_schema.TryMatchKey(mappingDefinitions, nextKey.Value, out String nextValueType))
                {
                    var nextValueDefinition = new DefinitionInfo(definition, nextValueType);
                    var nextValue           = Evaluate(nextValueDefinition);
                    mapping.Add(nextKey, nextValue);
                    continue;
                }

                // Loose
                if (looseKeyType != null)
                {
                    if (looseKeyDefinition == null)
                    {
                        looseKeyDefinition   = new DefinitionInfo(definition, looseKeyType);
                        looseValueDefinition = new DefinitionInfo(definition, looseValueType);
                    }

                    Validate(nextKey, looseKeyDefinition.Value);
                    var nextValue = Evaluate(looseValueDefinition.Value);
                    mapping.Add(nextKey, nextValue);
                    continue;
                }

                // Error
                m_context.Error(nextKey, TemplateStrings.UnexpectedValue(nextKey.Value));
                m_unraveler.SkipMappingValue();
            }

            // Only one
            if (mappingDefinitions.Count > 1)
            {
                var hitCount = new Dictionary <String, Int32>();
                foreach (MappingDefinition mapdef in mappingDefinitions)
                {
                    foreach (String key in mapdef.Properties.Keys)
                    {
                        if (!hitCount.TryGetValue(key, out Int32 value))
                        {
                            hitCount.Add(key, 1);
                        }
                        else
                        {
                            hitCount[key] = value + 1;
                        }
                    }
                }

                List <String> nonDuplicates = new List <String>();
                foreach (String key in hitCount.Keys)
                {
                    if (hitCount[key] == 1)
                    {
                        nonDuplicates.Add(key);
                    }
                }
                nonDuplicates.Sort();

                String listToDeDuplicate = String.Join(", ", nonDuplicates);
                m_context.Error(mapping, TemplateStrings.UnableToDetermineOneOf(listToDeDuplicate));
            }

            m_unraveler.ReadMappingEnd();
        }
Ejemplo n.º 15
0
 public TemplateValidationException(IEnumerable <TemplateValidationError> errors)
     : this(TemplateStrings.TemplateNotValidWithErrors(string.Join(",", (errors ?? Enumerable.Empty <TemplateValidationError>()).Select(e => e.Message))))
 {
     m_errors = new List <TemplateValidationError>(errors ?? Enumerable.Empty <TemplateValidationError>());
 }
Ejemplo n.º 16
0
 public TemplateValidationException()
     : this(TemplateStrings.TemplateNotValid())
 {
 }
Ejemplo n.º 17
0
        private TemplateToken ReadValue(DefinitionInfo definition)
        {
            m_memory.IncrementEvents();

            // Scalar
            if (m_objectReader.AllowLiteral(out LiteralToken literal))
            {
                var scalar = ParseScalar(literal, definition.AllowedContext);
                Validate(ref scalar, definition);
                m_memory.AddBytes(scalar);
                return(scalar);
            }

            // Sequence
            if (m_objectReader.AllowSequenceStart(out SequenceToken sequence))
            {
                m_memory.IncrementDepth();
                m_memory.AddBytes(sequence);

                var sequenceDefinition = definition.Get <SequenceDefinition>().FirstOrDefault();

                // Legal
                if (sequenceDefinition != null)
                {
                    var itemDefinition = new DefinitionInfo(definition, sequenceDefinition.ItemType);

                    // Add each item
                    while (!m_objectReader.AllowSequenceEnd())
                    {
                        var item = ReadValue(itemDefinition);
                        sequence.Add(item);
                    }
                }
                // Illegal
                else
                {
                    // Error
                    m_context.Error(sequence, TemplateStrings.UnexpectedSequenceStart());

                    // Skip each item
                    while (!m_objectReader.AllowSequenceEnd())
                    {
                        SkipValue();
                    }
                }

                m_memory.DecrementDepth();
                return(sequence);
            }

            // Mapping
            if (m_objectReader.AllowMappingStart(out MappingToken mapping))
            {
                m_memory.IncrementDepth();
                m_memory.AddBytes(mapping);

                var mappingDefinitions = definition.Get <MappingDefinition>().ToList();

                // Legal
                if (mappingDefinitions.Count > 0)
                {
                    if (mappingDefinitions.Count > 1 ||
                        m_schema.HasProperties(mappingDefinitions[0]) ||
                        String.IsNullOrEmpty(mappingDefinitions[0].LooseKeyType))
                    {
                        HandleMappingWithWellKnownProperties(definition, mappingDefinitions, mapping);
                    }
                    else
                    {
                        var keyDefinition   = new DefinitionInfo(definition, mappingDefinitions[0].LooseKeyType);
                        var valueDefinition = new DefinitionInfo(definition, mappingDefinitions[0].LooseValueType);
                        HandleMappingWithAllLooseProperties(definition, keyDefinition, valueDefinition, mapping);
                    }
                }
                // Illegal
                else
                {
                    m_context.Error(mapping, TemplateStrings.UnexpectedMappingStart());

                    while (!m_objectReader.AllowMappingEnd())
                    {
                        SkipValue();
                        SkipValue();
                    }
                }

                m_memory.DecrementDepth();
                return(mapping);
            }

            throw new InvalidOperationException(TemplateStrings.ExpectedScalarSequenceOrMapping());
        }
Ejemplo n.º 18
0
        private ScalarToken ParseScalar(
            LiteralToken token,
            String[] allowedContext)
        {
            // Not a string
            if (token.Type != TokenType.String)
            {
                return(token);
            }

            // Check if the value is definitely a literal
            var   raw = token.ToString();
            Int32 startExpression;

            if (String.IsNullOrEmpty(raw) ||
                (startExpression = raw.IndexOf(TemplateConstants.OpenExpression)) < 0) // Doesn't contain ${{
            {
                return(token);
            }

            // Break the value into segments of LiteralToken and ExpressionToken
            var segments = new List <ScalarToken>();
            var i        = 0;

            while (i < raw.Length)
            {
                // An expression starts here:
                if (i == startExpression)
                {
                    // Find the end of the expression - i.e. }}
                    startExpression = i;
                    var endExpression = -1;
                    var inString      = false;
                    for (i += TemplateConstants.OpenExpression.Length; i < raw.Length; i++)
                    {
                        if (raw[i] == '\'')
                        {
                            inString = !inString; // Note, this handles escaped single quotes gracefully. Ex. 'foo''bar'
                        }
                        else if (!inString && raw[i] == '}' && raw[i - 1] == '}')
                        {
                            endExpression = i;
                            i++;
                            break;
                        }
                    }

                    // Check if not closed
                    if (endExpression < startExpression)
                    {
                        m_context.Error(token, TemplateStrings.ExpressionNotClosed());
                        return(token);
                    }

                    // Parse the expression
                    var rawExpression = raw.Substring(
                        startExpression + TemplateConstants.OpenExpression.Length,
                        endExpression - startExpression + 1 - TemplateConstants.OpenExpression.Length - TemplateConstants.CloseExpression.Length);
                    var expression = ParseExpression(token.Line, token.Column, rawExpression, allowedContext, out Exception ex);

                    // Check for error
                    if (ex != null)
                    {
                        m_context.Error(token, ex);
                        return(token);
                    }

                    // Check if a directive was used when not allowed
                    if (!String.IsNullOrEmpty(expression.Directive) &&
                        ((startExpression != 0) || (i < raw.Length)))
                    {
                        m_context.Error(token, TemplateStrings.DirectiveNotAllowedInline(expression.Directive));
                        return(token);
                    }

                    // Add the segment
                    segments.Add(expression);

                    // Look for the next expression
                    startExpression = raw.IndexOf(TemplateConstants.OpenExpression, i);
                }
                // The next expression is further ahead:
                else if (i < startExpression)
                {
                    // Append the segment
                    AddString(segments, token.Line, token.Column, raw.Substring(i, startExpression - i));

                    // Adjust the position
                    i = startExpression;
                }
                // No remaining expressions:
                else
                {
                    AddString(segments, token.Line, token.Column, raw.Substring(i));
                    break;
                }
            }

            // Check if can convert to a literal
            // For example, the escaped expression: ${{ '{{ this is a literal }}' }}
            if (segments.Count == 1 &&
                segments[0] is BasicExpressionToken basicExpression &&
                IsExpressionString(basicExpression.Expression, out String str))
            {
                return(new StringToken(m_fileId, token.Line, token.Column, str));
            }

            // Check if only ony segment
            if (segments.Count == 1)
            {
                return(segments[0]);
            }

            // Build the new expression, using the format function
            var format   = new StringBuilder();
            var args     = new StringBuilder();
            var argIndex = 0;

            foreach (var segment in segments)
            {
                if (segment is StringToken literal)
                {
                    var text = ExpressionUtility.StringEscape(literal.Value) // Escape quotes
                               .Replace("{", "{{")                           // Escape braces
                               .Replace("}", "}}");
                    format.Append(text);
                }
                else
                {
                    format.Append("{" + argIndex.ToString(CultureInfo.InvariantCulture) + "}"); // Append formatter
                    argIndex++;

                    var expression = segment as BasicExpressionToken;
                    args.Append(", ");
                    args.Append(expression.Expression);
                }
            }

            return(new BasicExpressionToken(m_fileId, token.Line, token.Column, $"format('{format}'{args})"));
        }