private static List <ConceptType> CreateConceptTypesAndMembers(IEnumerable <Type> conceptInfoTypes) { IncludeBaseConceptTypes(ref conceptInfoTypes); var types = conceptInfoTypes .Distinct() .ToDictionary( conceptInfoType => conceptInfoType, conceptInfoType => CreateConceptTypePartial(conceptInfoType)); foreach (var type in types) { type.Value.Members = ConceptMembers.Get(type.Key) .Select(conceptMember => { var memberSyntax = new ConceptMemberSyntax(); ConceptMemberBase.Copy(conceptMember, memberSyntax); memberSyntax.ConceptType = memberSyntax.IsConceptInfo && !memberSyntax.IsConceptInfoInterface ? types.GetValue(conceptMember.ValueType, $"{nameof(DslSyntaxFromPlugins)} does not contain concept type '{conceptMember.ValueType}'," + $" referenced by {type.Key}.{conceptMember.Name}.") : null; return(memberSyntax); }) .ToList(); type.Value.BaseTypes = GetBaseConceptInfoTypes(type.Key) .Select(baseType => types[baseType]) .ToList(); } return(types.Values.OrderBy(conceptType => conceptType.AssemblyQualifiedName).ToList()); }
private void AppendMember(StringBuilder text, ConceptMemberSyntax member) { object memberValue = member.GetMemberValue(this); if (memberValue == null) { text.Append("<null>"); } else if (member.IsConceptInfo) { var value = (ConceptSyntaxNode)memberValue; if (member.IsConceptInfoInterface) { text.Append(value.Concept.GetRootTypeName()).Append(':'); } value.AppendKeyMembers(text); } else if (member.IsStringType) { ConceptMemberHelper.AppendWithQuotesIfNeeded(text, (string)memberValue); } else { throw new FrameworkException( $"{nameof(ConceptSyntaxNode)} member {member.Name} of type {member.ConceptType?.TypeName} in {Concept.TypeName} is not supported."); } }
private ValueOrError <object> ReadMemberValue(ConceptMemberSyntax member, ITokenReader tokenReader, ConceptSyntaxNode useLastConcept, bool firstMember, ref bool parsedFirstReferenceElement, bool readingAReference) { try { if (member.IsStringType) { if (useLastConcept != null) { return(ValueOrError <object> .CreateError($"This concept cannot be nested within {useLastConcept.Concept.TypeName}. Trying to read {_conceptType.TypeName}.")); } if (readingAReference && parsedFirstReferenceElement) { if (!tokenReader.TryRead(".")) { return(ValueOrError <object> .CreateError(string.Format( "Parent property and the following key value ({0}) must be separated with a dot. Expected \".\"", member.Name))); } } parsedFirstReferenceElement = true; // Legacy syntax: if (!readingAReference && member.IsKey && member.IsStringType && !firstMember) { if (tokenReader.TryRead(".")) { AddWarning($"Obsolete syntax: Remove '.' from {_conceptType.Keyword} statement. {((TokenReader)tokenReader).ReportPosition()}."); } } return(tokenReader.ReadText().ChangeType <object>()); } if (member.IsConceptInfoInterface) { if (useLastConcept != null) { return((object)useLastConcept); } else { return(ValueOrError <object> .CreateError($"Member of type IConceptInfo can only be nested within" + $" the referenced parent concept. It must be a first member or marked with {nameof(ConceptParentAttribute)}.")); } } // If the 'member' is not IsStringType (see above) and not IsConceptInfoInterface (see above), then it is a concrete IConceptInfo implementation type, and member.ConceptType should not be null. if (member.ConceptType == null) { throw new InvalidOperationException($"If the member type is not string or interface, it should have a ConceptType value. Member '{member.Name}' while reading {_conceptType.TypeName}."); } if (useLastConcept != null && member.ConceptType.IsInstanceOfType(useLastConcept)) { return((object)useLastConcept); } if (member.IsConceptInfo && firstMember && !readingAReference && _conceptType.Members.Count(m => m.IsParsable) == 1) { // This validation is not necessary for consistent parsing. It is enforced simply to avoid ambiguity for future concept overloads // when parsing similar concepts such as "Logging { AllProperties; }", "History { AllProperties; }" and "Persisted { AllProperties; }". var parentMembers = member.ConceptType.Members.Where(m => m.IsParsable).ToArray(); if (parentMembers.Length == 1 && parentMembers.Single().IsConceptInfo) { return(ValueOrError.CreateError($"{_conceptType.GetKeywordOrTypeName()} must be nested" + $" within the referenced parent concept {member.ConceptType.GetKeywordOrTypeName()}." + $" A single-reference concept that references another single-reference concept must always be used with nested syntax to avoid ambiguity.")); } } if (member.IsConceptInfo) { if (firstMember && member.ConceptType == _conceptType) { return(ValueOrError.CreateError(string.Format( "Recursive concept '{0}' cannot be used as a root because its parent property ({1}) must reference another concept." + " Use a non-recursive concept for the root and a derivation of the root concept with additional parent property as a recursive concept.", _conceptType.TypeName, member.Name))); } var subParser = new GenericParser(member.ConceptType); return(subParser.ParseMembers(tokenReader, useLastConcept, true, ref parsedFirstReferenceElement).ChangeType <object>()); } return(ValueOrError.CreateError(string.Format( "GenericParser does not support members of type \"{0}\". Try using string or implementation of IConceptInfo.", member.ConceptType.TypeName))); } catch (DslSyntaxException ex) { return(ValueOrError <object> .CreateError(ex.Message)); } }