public override List <Diagnostic> VisitStructuredTemplateBody([NotNull] LGFileParser.StructuredTemplateBodyContext context)
            {
                var result = new List <Diagnostic>();

                var typeName = context.structuredBodyNameLine().STRUCTURED_CONTENT().GetText().Trim();

                if (!structuredNameRegex.IsMatch(typeName))
                {
                    result.Add(BuildLGDiagnostic($"Structured type {typeName} is invalid. Letter, number, '_', '-' and '.' is allowed.", context: context.structuredBodyContentLine()));
                }

                var bodys = context.structuredBodyContentLine()?.STRUCTURED_CONTENT();

                if (bodys == null || bodys.Length == 0 || bodys.All(u => string.IsNullOrEmpty(u.GetText())))
                {
                    result.Add(BuildLGDiagnostic($"Structured content is empty", context: context.structuredBodyContentLine()));
                }
                else
                {
                    foreach (var body in bodys)
                    {
                        var line = body.GetText().Trim();
                        if (!string.IsNullOrWhiteSpace(line))
                        {
                            var start = line.IndexOf('=');
                            if (start < 0 && !Evaluator.IsPureExpression(line))
                            {
                                result.Add(BuildLGDiagnostic($"Structured content does not support", context: context.structuredBodyContentLine()));
                            }
                            else
                            {
                                if (start > 0)
                                {
                                    var property    = line.Substring(0, start).Trim().ToLower();
                                    var originValue = line.Substring(start + 1).Trim();
                                    var valueArray  = Evaluator.EscapeSeperatorRegex.Split(originValue);

                                    if (valueArray.Length == 1)
                                    {
                                        result.AddRange(CheckText(originValue, context.structuredBodyContentLine()));
                                    }
                                    else
                                    {
                                        foreach (var item in valueArray)
                                        {
                                            result.AddRange(CheckText(item.Trim(), context.structuredBodyContentLine()));
                                        }
                                    }
                                }
                                else if (Evaluator.IsPureExpression(line))
                                {
                                    result.AddRange(CheckExpression(line, context.structuredBodyContentLine()));
                                }
                            }
                        }
                    }
                }

                return(result);
            }
        private List <string> EvalText(string exp)
        {
            if (string.IsNullOrEmpty(exp))
            {
                return(new List <string>()
                {
                    exp
                });
            }

            if (Evaluator.IsPureExpression(exp))
            {
                // @{} or {} text, get object result
                return(EvalExpression(exp));
            }
            else
            {
                return(EvalTextContainsExpression(exp).Select(x => Regex.Unescape(x)).ToList());
            }
        }
        public override List <string> VisitStructuredBody([NotNull] LGFileParser.StructuredBodyContext context)
        {
            var idToStringDict = new Dictionary <string, string>();
            var stb            = context.structuredTemplateBody();
            var result         = new JObject();
            var typeName       = stb.structuredBodyNameLine().STRUCTURED_CONTENT().GetText().Trim();

            result["$type"] = typeName;
            var expandedResult = new List <JObject>();

            expandedResult.Add(result);
            var bodys = stb.structuredBodyContentLine().STRUCTURED_CONTENT();

            foreach (var body in bodys)
            {
                var line = body.GetText().Trim();

                if (string.IsNullOrWhiteSpace(line))
                {
                    continue;
                }

                var start = line.IndexOf('=');
                if (start > 0)
                {
                    // make it insensitive
                    var property    = line.Substring(0, start).Trim().ToLower();
                    var originValue = line.Substring(start + 1).Trim();

                    var valueArray = Evaluator.EscapeSeperatorRegex.Split(originValue);
                    if (valueArray.Length == 1)
                    {
                        var id = Guid.NewGuid().ToString();
                        expandedResult.ForEach(x => x[property] = id);
                        idToStringDict.Add(id, originValue);
                    }
                    else
                    {
                        var valueList = new JArray();
                        foreach (var item in valueArray)
                        {
                            var id = Guid.NewGuid().ToString();
                            valueList.Add(id);
                            idToStringDict.Add(id, item.Trim());
                        }

                        expandedResult.ForEach(x => x[property] = valueList);
                    }
                }
                else if (Evaluator.IsPureExpression(line))
                {
                    // [MyStruct
                    // Text = foo
                    // {ST2()}
                    // ]

                    // When the same property exists in both the calling template as well as callee, the content in caller will trump any content in the callee.
                    var propertyObjects = EvalExpression(line).Select(x => JObject.Parse(x)).ToList();
                    var tempResult      = new List <JObject>();
                    foreach (var res in expandedResult)
                    {
                        foreach (var propertyObject in propertyObjects)
                        {
                            var tempRes = JObject.FromObject(res);

                            // Full reference to another structured template is limited to the structured template with same type
                            if (propertyObject["$type"] != null && propertyObject["$type"].ToString() == typeName)
                            {
                                foreach (var item in propertyObject)
                                {
                                    if (tempRes.Property(item.Key) == null)
                                    {
                                        tempRes[item.Key] = item.Value;
                                    }
                                }
                            }

                            tempResult.Add(tempRes);
                        }
                    }

                    expandedResult = tempResult;
                }
            }

            var exps = expandedResult.Select(x => JsonConvert.SerializeObject(x)).ToList();
            var templateRefValues = new Dictionary <string, List <string> >();

            foreach (var idToString in idToStringDict)
            {
                // convert id text or expression to list of evaluated values
                if ((idToString.Value.StartsWith("@") || idToString.Value.EndsWith("{")) && idToString.Value.EndsWith("}"))
                {
                    templateRefValues.Add(idToString.Key, this.EvalExpression(idToString.Value));
                }
                else
                {
                    templateRefValues.Add(idToString.Key, this.EvalText(idToString.Value));
                }
            }

            var finalResult = new List <string>(exps);

            foreach (var templateRefValue in templateRefValues)
            {
                var tempRes = new List <string>();
                foreach (var res in finalResult)
                {
                    foreach (var refValue in templateRefValue.Value)
                    {
                        tempRes.Add(res.Replace(templateRefValue.Key, refValue));
                    }
                }

                finalResult = tempRes;
            }

            return(finalResult);
        }