public static IConceptInfo ConvertNodeToConceptInfo(ConceptSyntaxNode node, Dictionary <ConceptSyntaxNode, IConceptInfo> keepReferences = null) { Type conceptInfoType = Type.GetType(node.Concept.AssemblyQualifiedName); if (conceptInfoType == null) { throw new ArgumentException($"Cannot find concept type '{node.Concept.AssemblyQualifiedName}'."); } var ci = (IConceptInfo)Activator.CreateInstance(conceptInfoType); var members = ConceptMembers.Get(conceptInfoType); if (node.Parameters.Length != members.Length) { throw new InvalidOperationException( $"{nameof(ConceptSyntaxNode)} parameters count ({node.Parameters.Length})" + $" does not match {nameof(ConceptMembers)} count ({members.Length})."); } for (int m = 0; m < members.Length; m++) { var member = members[m]; object value = node.Parameters[m]; if (value == null) { member.SetMemberValue(ci, null); } else if (value is string) { member.SetMemberValue(ci, value); } else if (value is ConceptSyntaxNode referencedNode) { IConceptInfo referencedConceptInfo; if (keepReferences == null || !keepReferences.TryGetValue(referencedNode, out referencedConceptInfo)) { referencedConceptInfo = ConvertNodeToConceptInfo(referencedNode, keepReferences); } member.SetMemberValue(ci, referencedConceptInfo); } else { var valueType = value?.GetType().Name ?? "null"; throw new ArgumentException($"Value type {valueType} is not expected in {nameof(ConceptSyntaxNode)} parameter {node.Concept.TypeName}.{member.Name}."); } } if (keepReferences != null) { keepReferences[node] = ci; } return(ci); }
private string ReportPreviousConcept(ConceptSyntaxNode node) { var sb = new StringBuilder(); if (node != null) { sb.AppendFormat("Previous concept: {0}", node.GetUserDescription()).AppendLine(); foreach (var m in node.Concept.Members) { sb.AppendFormat("Property '{0}' ({1}) = {2}", m.Name, m.IsStringType ? "string" : m.IsConceptInfoInterface ? "IConceptInfo" : m.ConceptType?.TypeName, m.GetMemberValue(node)?.ToString() ?? "<null>") .AppendLine(); } } return(sb.ToString()); }
private ValueOrError <ConceptSyntaxNode> ParseMembers(ITokenReader tokenReader, ConceptSyntaxNode useLastConcept, bool readingAReference, ref bool parsedFirstReferenceElement) { ConceptSyntaxNode node = new ConceptSyntaxNode(_conceptType); bool firstMember = true; var listOfMembers = readingAReference ? _conceptType.Members.Where(m => m.IsKey) : _conceptType.Members.Where(m => m.IsParsable); var parentProperty = GetParentProperty(listOfMembers); if (useLastConcept != null && parentProperty == null) { return(ValueOrError <ConceptSyntaxNode> .CreateError($"This concept cannot be nested within {useLastConcept.Concept.TypeName}. Trying to read {_conceptType.TypeName}.")); } foreach (ConceptMemberSyntax member in listOfMembers) { if (!readingAReference) { parsedFirstReferenceElement = false; // Reset a reference elements group, that should separated by dot. } var valueOrError = ReadMemberValue(member, tokenReader, member == parentProperty ? useLastConcept : null, firstMember, ref parsedFirstReferenceElement, readingAReference); OnMemberRead?.Invoke(tokenReader, node, member, valueOrError); if (valueOrError.IsError) { return(ValueOrError <ConceptSyntaxNode> .CreateError(string.Format(CultureInfo.InvariantCulture, "Cannot read the value of {0} in {1}. {2}", member.Name, _conceptType.TypeName, valueOrError.Error))); } member.SetMemberValue(node, valueOrError.Value); firstMember = false; } return(ValueOrError <ConceptSyntaxNode> .CreateValue(node)); }
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)); } }
private void UpdateContextForNextConcept(TokenReader tokenReader, Stack <ConceptSyntaxNode> context, ConceptSyntaxNode conceptInfo) { if (tokenReader.TryRead("{")) { context.Push(conceptInfo); OnUpdateContext?.Invoke(tokenReader, context, true); } else if (!tokenReader.TryRead(";")) { var(dslScript, position) = tokenReader.GetPositionInScript(); throw new DslSyntaxException("Expected \";\" or \"{\".", "RH0001", dslScript, position, 0, ReportPreviousConcept(conceptInfo)); } while (tokenReader.TryRead("}")) { if (context.Count == 0) { var simpleMessage = "Unexpected \"}\"."; var(dslScript, position) = tokenReader.GetPositionInScript(); throw new DslSyntaxException(simpleMessage, "RH0007", dslScript, position, 0, null); } context.Pop(); OnUpdateContext?.Invoke(tokenReader, context, false); } }
/// <summary> /// Determines whether the specified syntax node is an instance of the current (base property) concept type. /// </summary> /// <remarks> /// Before calling this method, check if the base property has <see cref="ConceptMemberSyntax.ConceptType"/> set, /// to avoid null reference exception. /// In case of the null value, consider including the base properties with <see cref="ConceptMemberBase.IsConceptInfoInterface"/> set, /// because any derived concept type can also be assigned to the base property of type <see cref="IConceptInfo"/>. /// </remarks> public bool IsInstanceOfType(ConceptSyntaxNode derivedTypeNode) { return(IsAssignableFrom(derivedTypeNode.Concept)); }
public object GetMemberValue(ConceptSyntaxNode node) { return(node.Parameters[Index]); }
public void SetMemberValue(ConceptSyntaxNode node, object value) { node.Parameters[Index] = value; }