private StatEntity InstantiateEntityInternal(StatSubtypeDefinition subtype, string declarationName,
                                                     CodeLocation location, Dictionary <string, object> properties, Dictionary <string, CodeLocation> propertyLocations)
        {
            var entity = new StatEntity
            {
                Name              = declarationName,
                Type              = subtype,
                BaseClass         = null, // FIXME
                Location          = location,
                Properties        = new Dictionary <string, object>(),
                PropertyLocations = propertyLocations
            };

            foreach (var property in properties)
            {
                if (property.Key == "EntityType")
                {
                    continue;
                }

                propertyLocations.TryGetValue(property.Key, out CodeLocation propLocation);
                var parsed = ParseProperty(subtype, property.Key, property.Value, propLocation, declarationName);
                if (parsed != null)
                {
                    entity.Properties.Add(property.Key, parsed);
                }
            }

            return(entity);
        }
        private void AddSubtype(StatTypeDefinition definition, string subtypeName, IEnumerable <XElement> fields)
        {
            var subtype = new StatSubtypeDefinition(definition, subtypeName);

            foreach (var field in fields)
            {
                AddField(definition, subtype, field);
            }

            definition.Subtypes.Add(subtypeName, subtype);
        }
        private object ParseProperty(StatSubtypeDefinition subtype, string propertyName, object value, CodeLocation location,
                                     string declarationName)
        {
            if (!subtype.Fields.TryGetValue(propertyName, out StatField field))
            {
                Context.LogError(DiagnosticCode.StatPropertyUnsupported, $"Property '{propertyName}' is not supported on {subtype.Name} '{declarationName}'",
                                 location?.FileName, location?.StartLine ?? 0, declarationName);
                return(null);
            }

            bool   succeeded = false;
            string errorText = null;
            object parsed;

            if (field.Type != "Passthrough")
            {
                var parser = field.GetParser(ParserFactory, Context.Definitions);
                parsed = parser.Parse((string)value, ref succeeded, ref errorText);
            }
            else
            {
                parsed    = value;
                succeeded = true;
            }

            if (errorText != null)
            {
                Context.LogError(DiagnosticCode.StatPropertyValueInvalid, $"{subtype.Name} '{declarationName}' has invalid {propertyName}: '{value}' ({errorText})",
                                 location?.FileName, location?.StartLine ?? 0, declarationName);
            }

            if (succeeded)
            {
                return(parsed);
            }
            else
            {
                return(null);
            }
        }
        private void AddField(StatTypeDefinition definition, StatSubtypeDefinition subtype, XElement field)
        {
            if (field.Attribute("export_name").Value == "")
            {
                return;
            }

            var             fieldName   = field.Attribute("export_name").Value;
            var             typeName    = field.Attribute("type").Value;
            StatEnumeration enumeration = null;
            List <StatReferenceConstraint> referenceConstraints = null;

            switch (typeName)
            {
            case "Enumeration":
            case "EnumerationList":
                var enumName = field.Attribute("enumeration_type_name").Value;
                enumeration = Enumerations[enumName];
                break;

            case "Name":
                if (definition.NameProperty == null)
                {
                    definition.NameProperty = fieldName;
                }
                else if (definition.NameProperty != fieldName)
                {
                    throw new Exception($"Conflicting Name property for type '{definition.Name}': First seen using '{definition.NameProperty}', now seen using '{fieldName}'.");
                }
                break;

            case "BaseClass":
                if (definition.BaseClassProperty == null)
                {
                    definition.BaseClassProperty = fieldName;
                }
                else if (definition.BaseClassProperty != fieldName)
                {
                    throw new Exception($"Conflicting BaseClass for type '{definition.Name}': First seen using '{definition.BaseClassProperty}', now seen using '{fieldName}'.");
                }
                break;

            case "StatReference":
            case "StatReferences":
                referenceConstraints = new List <StatReferenceConstraint>();
                var descriptions = field.Element("stat_descriptions");
                if (descriptions == null)
                {
                    throw new Exception("Field of type 'StatReference' must have a list of stat types in the <stat_descriptions> node");
                }

                var descs = descriptions.Elements("description");
                foreach (var desc in descs)
                {
                    var constraint = new StatReferenceConstraint
                    {
                        StatType    = desc.Attribute("stat_type").Value,
                        StatSubtype = desc.Attribute("stat_subtype")?.Value ?? null
                    };
                    referenceConstraints.Add(constraint);
                }

                break;

            case "Boolean":
            case "Integer":
            case "Float":
            case "String":
            case "TranslatedString":
            case "RootTemplate":
            case "Comment":
            case "Color":
            case "Requirements":
            case "Properties":
            case "Conditions":
            case "Passthrough":
            case "UUID":
                break;

            default:
                throw new Exception($"Unsupported stat field type: '{typeName}'");
            }

            var statField = new StatField
            {
                Name           = fieldName,
                Type           = typeName,
                EnumType       = enumeration,
                ReferenceTypes = referenceConstraints
            };

            subtype.Fields.Add(fieldName, statField);

            if (typeName == "TranslatedString")
            {
                var translatedKeyRefField = new StatField
                {
                    Name     = fieldName + "Ref",
                    Type     = typeName,
                    EnumType = enumeration
                };
                subtype.Fields.Add(fieldName + "Ref", translatedKeyRefField);
            }
        }
 private StatEntity InstantiateEntity(StatSubtypeDefinition subtype, string declarationName, StatDeclaration declaration)
 {
     return(InstantiateEntityInternal(subtype, declarationName, declaration.Location,
                                      declaration.Properties, declaration.PropertyLocations));
 }