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 bool ConteptsValueEqualOrBase(IConceptInfo newConcept, IConceptInfo existingConcept) { if (object.ReferenceEquals(newConcept, existingConcept)) { return(true); } else if (newConcept.GetKey() != existingConcept.GetKey()) { return(false); } else if (!newConcept.GetType().IsInstanceOfType(existingConcept)) { return(false); } else { var newConceptMemebers = ConceptMembers.Get(newConcept); foreach (ConceptMember member in newConceptMemebers) { if (member.IsKey) { continue; } if (!IsConceptMemberEqual(newConcept, existingConcept, member)) { return(false); } } } return(true); }
/// <summary> /// Use only for generating an error details. Returns the concept's description ignoring possible null reference errors. /// </summary> public static string GetErrorDescription(this IConceptInfo ci) { if (ci == null) { return("<null>"); } var report = new StringBuilder(); report.Append(ci.GetType().FullName); foreach (var member in ConceptMembers.Get(ci)) { report.Append(" " + member.Name + "="); var memberValue = member.GetValue(ci); try { if (memberValue == null) { report.Append("<null>"); } else if (member.IsConceptInfo) { AppendMembers(report, (IConceptInfo)memberValue, SerializationOptions.KeyMembers, exceptionOnNullMember: false); } else { report.Append(memberValue.ToString()); } } catch (Exception ex) { report.Append("<" + ex.GetType().Name + ">"); } } return(report.ToString()); }
public GenericParser(Type conceptInfoType, string keyword) { Contract.Requires(conceptInfoType != null); Contract.Requires(keyword != null); this.ConceptInfoType = conceptInfoType; this.Keyword = keyword; Members = ConceptMembers.Get(conceptInfoType).ToArray(); }
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 static List <Type> GetNestingOptions(Type conceptType) { var options = new List <Type>(); while (!options.Contains(conceptType)) // Recursive concept are possible. { options.Add(conceptType); if (typeof(IConceptInfo).IsAssignableFrom(conceptType)) { conceptType = ConceptMembers.Get(conceptType).First().ValueType; } } return(options); }
private static void AddAllDependencies(IConceptInfo conceptInfo, ICollection <IConceptInfo> dependencies) { foreach (var member in ConceptMembers.Get(conceptInfo)) { if (member.IsConceptInfo) { var dependency = (IConceptInfo)member.GetValue(conceptInfo); if (!dependencies.Contains(dependency)) { dependencies.Add(dependency); AddAllDependencies(dependency, dependencies); } } } }
/// <summary> /// Since DSL parser and macro evaluators return stub references, this function replaces each reference with actual instance of the referenced concept. /// Note: This method could handle circular dependencies between the concepts, but for simplicity of the implementation this is currently not supported. /// </summary> private void ReplaceReferencesWithFullConcepts(ConceptDescription conceptDesc) { var references = ConceptMembers.Get(conceptDesc.Concept).Where(member => member.IsConceptInfo) .Select(member => new UnresolvedReference(conceptDesc, member)); foreach (var reference in references) { ReplaceReferenceWithFullConceptOrMarkUnresolved(reference); } if (conceptDesc.UnresolvedDependencies == 0) { MarkResolvedConcept(conceptDesc); } }
public void Add(IConceptInfo concept) { Type conceptType = concept.GetType(); _subtypes.Add(conceptType); foreach (ConceptMember member in ConceptMembers.Get(concept)) { if (member.IsConceptInfo) { string referencedConceptKey = ((IConceptInfo)member.GetValue(concept)).GetKey(); _conceptsIndex.Add(referencedConceptKey, conceptType, member.Name, concept); } } }
private void Disambiguate(List <Interpretation> possibleInterpretations) { // Interpretation that covers most of the DSL script has priority, // because other interpretations are obviously missing some parameters, // otherwise the parser would stop earlier on '{' or ';'. int largest = possibleInterpretations.Max(i => i.NextPosition.PositionInTokenList); possibleInterpretations.RemoveAll(i => i.NextPosition.PositionInTokenList < largest); if (possibleInterpretations.Count == 1) { return; } // Interpretation with a flat syntax has priority over the interpretation // that could be placed in a nested concept. // The nested interpretation can be manually enforced in DSL script (if needed) // by nesting this concept. var interpretationParameters = possibleInterpretations .Select(i => { var firstMemberType = ConceptMembers.Get(i.ConceptInfo).First().ValueType; return(new { Interpretation = i, FirstParameter = firstMemberType, NestingOptions = GetNestingOptions(firstMemberType) }); }) .ToList(); var couldBeNested = new HashSet <Interpretation>(); foreach (var i1 in interpretationParameters) { foreach (var i2 in interpretationParameters) { if (i1 != i2 && i2.NestingOptions.Skip(1).Contains(i1.FirstParameter)) { couldBeNested.Add(i2.Interpretation); _logger.Trace(() => $"Interpretation {i1.Interpretation.ConceptInfo.GetType().Name}" + $" has priority over {i2.Interpretation.ConceptInfo.GetType().Name}," + $" because the second one could be nested in {i2.FirstParameter.Name} to force that interpretation." + $" Statement: {i1.Interpretation.ConceptInfo.GetUserDescription()}."); } } } var flatestInterpretations = possibleInterpretations.Except(couldBeNested).ToList(); if (flatestInterpretations.Count == 1) { possibleInterpretations.Clear(); possibleInterpretations.Add(flatestInterpretations.Single()); } }
private static void AddReferencesBeforeConcept(IConceptInfo concept, List <IConceptInfo> sortedList, Dictionary <IConceptInfo, bool> processed) { if (!processed.ContainsKey(concept)) { throw new FrameworkException(string.Format( "Unexpected inner state: Referenced concept {0} is not found in list of all concepts.", concept.GetUserDescription())); } if (processed[concept]) // eliminates duplication of referenced concepts and stops circular references from infinite recursion { return; } processed[concept] = true; foreach (ConceptMember member in ConceptMembers.Get(concept)) { if (member.IsConceptInfo) { AddReferencesBeforeConcept((IConceptInfo)member.GetValue(concept), sortedList, processed); } } sortedList.Add(concept); }
private static void AppendMembers(StringBuilder text, IConceptInfo ci, SerializationOptions serializationOptions, bool exceptionOnNullMember = false, Type asBaseConceptType = null) { var members = asBaseConceptType != null?ConceptMembers.Get(asBaseConceptType) : ConceptMembers.Get(ci); bool firstMember = true; for (int m = 0; m < members.Length; m++) { var member = members[m]; if (serializationOptions == SerializationOptions.AllMembers || member.IsKey) { string separator = member.IsKey ? "." : " "; if (!firstMember) { text.Append(separator); } firstMember = false; AppendMember(text, ci, member, exceptionOnNullMember); } } }
/// <summary> /// Since DSL parser returns stub references, this function replaces each reference with actual instance of the referenced concept. /// Function returns concepts that have newly resolved references. /// Note: This method could handle circular dependencies between the concepts, but for simplicity of the implementation this is currently not supported. /// </summary> private List <IConceptInfo> ReplaceReferencesWithFullConcepts(IEnumerable <ConceptDescription> newConceptsDesc) { var newlyResolved = new List <IConceptInfo>(); foreach (var conceptDesc in newConceptsDesc) { var references = ConceptMembers.Get(conceptDesc.Concept).Where(member => member.IsConceptInfo) .Select(member => new UnresolvedReference(conceptDesc, member)); foreach (var reference in references) { ReplaceReferenceWithFullConceptOrMarkUnresolved(reference); } if (conceptDesc.UnresolvedDependencies == 0) { newlyResolved.Add(conceptDesc.Concept); newlyResolved.AddRange(MarkResolvedConcept(conceptDesc)); } } return(newlyResolved); }
private static void AppendMembers(StringBuilder text, IConceptInfo ci, SerializationOptions serializationOptions, bool exceptionOnNullMember = false, Type asBaseConceptType = null) { IEnumerable <ConceptMember> members = ConceptMembers.Get(asBaseConceptType ?? ci.GetType()); if (serializationOptions == SerializationOptions.KeyMembers) { members = members.Where(member => member.IsKey); } bool firstMember = true; foreach (ConceptMember member in members) { string separator = member.IsKey ? "." : " "; if (!firstMember) { text.Append(separator); } firstMember = false; AppendMember(text, ci, member, exceptionOnNullMember); } }
/// <summary> /// Returns a list of concepts that this concept directly depends on. /// </summary> public static IEnumerable <IConceptInfo> GetDirectDependencies(this IConceptInfo conceptInfo) { return((from member in ConceptMembers.Get(conceptInfo) where member.IsConceptInfo select(IConceptInfo) member.GetValue(conceptInfo)).Distinct().ToList()); }
public GenericParser(Type conceptInfoType, string keyword) { ConceptInfoType = conceptInfoType; Keyword = keyword; Members = ConceptMembers.Get(conceptInfoType).ToArray(); }
/// <summary> /// Since DSL parser returns stub references, this function replaces each reference with actual instance of the referenced concept. /// Function returns concepts that have newly resolved references. /// </summary> private IEnumerable <IConceptInfo> ReplaceReferencesWithFullConcepts(bool errorOnUnresolvedReference) { var dependencies = new List <Tuple <string, string> >(); var newUnresolved = new List <string>(); foreach (var concept in _unresolvedConceptsByKey) { foreach (ConceptMember member in ConceptMembers.Get(concept.Value)) { if (member.IsConceptInfo) { var reference = (IConceptInfo)member.GetValue(concept.Value); if (reference == null) { string errorMessage = "Property '" + member.Name + "' is not initialized."; if (concept.Value is IAlternativeInitializationConcept) { errorMessage = errorMessage + string.Format( " Check if the InitializeNonparsableProperties function of IAlternativeInitializationConcept implementation at {0} is implemented properly.", concept.Value.GetType().Name); } throw new DslSyntaxException(concept.Value, errorMessage); } string referencedKey = reference.GetKey(); dependencies.Add(Tuple.Create(referencedKey, concept.Key)); IConceptInfo referencedConcept; if (!_resolvedConceptsByKey.TryGetValue(referencedKey, out referencedConcept) && !_unresolvedConceptsByKey.TryGetValue(referencedKey, out referencedConcept)) { if (errorOnUnresolvedReference) { throw new DslSyntaxException(concept.Value, string.Format( "Referenced concept is not defined in DSL scripts: '{0}'.", reference.GetUserDescription())); } newUnresolved.Add(concept.Key); } else { member.SetMemberValue(concept.Value, referencedConcept); } } } } // Unresolved concepts should alse include any concept with resolved references that references an unresolved concept. newUnresolved = Graph.IncludeDependents(newUnresolved, dependencies); var unresolvedIndex = new HashSet <string>(newUnresolved); var newlyResolved = _unresolvedConceptsByKey .Where(concept => !unresolvedIndex.Contains(concept.Key)) .ToList(); foreach (var concept in newlyResolved) { _logger.Trace(() => "New concept with resolved references: " + concept.Key); _unresolvedConceptsByKey.Remove(concept.Key); _resolvedConcepts.Add(concept.Value); _resolvedConceptsByKey.Add(concept.Key, concept.Value); foreach (var index in _dslModelIndexes) { index.Add(concept.Value); } } return(newlyResolved.Select(concept => concept.Value)); }
public ValueOrError <object> ReadMemberValue(ConceptMember member, ITokenReader tokenReader, IConceptInfo lastConcept, bool firstMember, ref bool lastPropertyWasInlineParent, ref bool lastConceptUsed, bool readingAReference) { try { if (lastPropertyWasInlineParent && member.IsKey && !member.IsConceptInfo) // TODO: Removing "IsConceptInfo" from this condition would produce a mismatch. Think of a better solution for parsing the concept key. { 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))); } } lastPropertyWasInlineParent = false; if (member.IsStringType) { return(tokenReader.ReadText().ChangeType <object>()); } if (member.ValueType == typeof(IConceptInfo)) { if (firstMember && lastConcept != null) { lastConceptUsed = true; return((object)lastConcept); } else { return(ValueOrError <object> .CreateError("Member of type IConceptInfo can only be used as a first member and enclosed within the referenced parent concept.")); } } if (member.IsConceptInfo && lastConcept != null && member.ValueType.IsInstanceOfType(lastConcept) && member.ValueType.IsAssignableFrom(ConceptInfoType)) // Recursive "parent" property { lastConceptUsed = true; return((object)lastConcept); } if (member.IsConceptInfo && lastConcept != null && member.ValueType.IsInstanceOfType(lastConcept) && firstMember) { lastConceptUsed = true; return((object)lastConcept); } if (member.IsConceptInfo && firstMember) { if (member.ValueType == ConceptInfoType) { 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.", ConceptInfoHelper.GetKeywordOrTypeName(ConceptInfoType), member.Name))); } if (!readingAReference && Members.Where(m => m.IsParsable).Count() == 1) { // This validation is not necessary for consistent parsing. It is enforced simply to avoid ambiguity when parsing // similar concepts such as "Logging { AllProperties; }", "History { AllProperties; }" and "Persisted { AllProperties; }". var parentMembers = ConceptMembers.Get(member.ValueType).Where(m => m.IsParsable).ToArray(); if (parentMembers.Count() == 1 && parentMembers.Single().IsConceptInfo) { return(ValueOrError.CreateError(string.Format( "{0} must be enclosed within the referenced parent concept {1}. A single-reference concept that references another single-reference concept must always be used with embedded syntax to avoid ambiguity.", ConceptInfoHelper.GetKeywordOrTypeName(ConceptInfoType), ConceptInfoHelper.GetKeywordOrTypeName(member.ValueType)))); } } GenericParser subParser = new GenericParser(member.ValueType, ""); lastConceptUsed = true; lastPropertyWasInlineParent = true; return(subParser.ParseMembers(tokenReader, lastConcept, true).ChangeType <object>()); } if (member.IsConceptInfo) { GenericParser subParser = new GenericParser(member.ValueType, ""); return(subParser.ParseMembers(tokenReader, null, true).ChangeType <object>()); } return(ValueOrError.CreateError(string.Format( "GenericParser does not support members of type \"{0}\". Try using string or implementation of IConceptInfo.", member.ValueType.Name))); } catch (DslSyntaxException ex) { return(ValueOrError <object> .CreateError(ex.Message)); } }
/// <summary> /// This method sorts concepts so that if concept A references concept B, /// in the resulting list B will be positioned somewhere before A. /// This will allow code generators to safely assume that the code for referenced concept B /// is already generated before the concept A inserts an additional code snippet into it. /// </summary> /// <param name="initialSort"> /// Initial sorting will reduce variations in the generated application source /// that are created by different macro evaluation order on each deployment. /// </param> public void SortReferencesBeforeUsingConcept(InitialConceptsSort initialSort) { var sw = Stopwatch.StartNew(); // Initial sort: if (initialSort != InitialConceptsSort.None) { var sortComparison = new Dictionary <InitialConceptsSort, Comparison <IConceptInfo> > { { InitialConceptsSort.Key, (a, b) => a.GetKey().CompareTo(b.GetKey()) }, { InitialConceptsSort.KeyDescending, (a, b) => - a.GetKey().CompareTo(b.GetKey()) }, }; _resolvedConcepts.Sort(sortComparison[initialSort]); _performanceLogger.Write(sw, $"SortReferencesBeforeUsingConcept: Initial sort by {initialSort}."); } // Extract dependencies: var dependencies = new List <(IConceptInfo dependsOn, IConceptInfo dependent)>(_resolvedConcepts.Count); foreach (var concept in _resolvedConcepts) { foreach (ConceptMember member in ConceptMembers.Get(concept)) { if (member.IsConceptInfo) { dependencies.Add(((IConceptInfo)member.GetValue(concept), concept)); } } } var dependents = dependencies.ToMultiDictionary(d => d.dependsOn, d => d.dependent); var countOfDependencies = dependencies.GroupBy(d => d.dependent).ToDictionary(group => group.Key, group => group.Count()); var conceptsWithoutDependencies = _resolvedConcepts.Where(c => !countOfDependencies.ContainsKey(c)).ToList(); conceptsWithoutDependencies = conceptsWithoutDependencies.Where(c => c is InitializationConcept) .Concat(conceptsWithoutDependencies.Where(c => !(c is InitializationConcept))) .ToList(); var newWithoutDependencies = new List <IConceptInfo>(conceptsWithoutDependencies.Count / 2); // Sort by dependencies: var sortedList = new List <IConceptInfo>(_resolvedConcepts.Count); while (sortedList.Count < _resolvedConcepts.Count) { if (!conceptsWithoutDependencies.Any()) { var unresolvedConcepts = countOfDependencies.Where(cd => cd.Value > 0).Select(c => c.Key).ToList(); int reportConcepts = 5; throw new FrameworkException($"Circular dependency detected while sorting concepts." + $" Unresolved {unresolvedConcepts.Count} concepts:" + $" {string.Join(", ", unresolvedConcepts.Take(reportConcepts).Select(c => c.GetUserDescription()))}" + (unresolvedConcepts.Count > reportConcepts ? ", ..." : ".")); } // Using a top-down breadth-first sorting, instead of a recursion, to provide more stable sort. // For example, adding a filter that references an entity should not change the ordering of the entities. newWithoutDependencies.Clear(); foreach (var concept in conceptsWithoutDependencies) { sortedList.Add(concept); foreach (var dependent in dependents.Get(concept)) { int remaining = --countOfDependencies[dependent]; if (remaining == 0) { newWithoutDependencies.Add(dependent); } } } conceptsWithoutDependencies.Clear(); conceptsWithoutDependencies.AddRange(newWithoutDependencies); } // Result: if (conceptsWithoutDependencies.Any()) { throw new FrameworkException($"Unexpected internal state: Remaining {conceptsWithoutDependencies.Count} {nameof(conceptsWithoutDependencies)} to resolve."); } if (countOfDependencies.Any(c => c.Value != 0)) { throw new FrameworkException($"Unexpected internal state: Remaining {nameof(countOfDependencies)} != 0."); } _resolvedConcepts.Clear(); _resolvedConcepts.AddRange(sortedList); _performanceLogger.Write(sw, "SortReferencesBeforeUsingConcept."); }
private ValueOrError <object> ReadMemberValue(ConceptMember member, ITokenReader tokenReader, IConceptInfo 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.GetType().Name}. Trying to read {ConceptInfoType.Name}.")); } 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 {Keyword} statement. {((TokenReader)tokenReader).ReportPosition()}."); } } return(tokenReader.ReadText().ChangeType <object>()); } if (member.ValueType == typeof(IConceptInfo)) { 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 (useLastConcept != null && member.ValueType.IsInstanceOfType(useLastConcept)) { return((object)useLastConcept); } if (member.IsConceptInfo && firstMember && !readingAReference && 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 = ConceptMembers.Get(member.ValueType).Where(m => m.IsParsable).ToArray(); if (parentMembers.Count() == 1 && parentMembers.Single().IsConceptInfo) { return(ValueOrError.CreateError($"{ConceptInfoHelper.GetKeywordOrTypeName(ConceptInfoType)} must be nested" + $" within the referenced parent concept {ConceptInfoHelper.GetKeywordOrTypeName(member.ValueType)}." + $" 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.ValueType == ConceptInfoType) { 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.", ConceptInfoHelper.GetKeywordOrTypeName(ConceptInfoType), member.Name))); } GenericParser subParser = new GenericParser(member.ValueType, ""); 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.ValueType.Name))); } catch (DslSyntaxException ex) { return(ValueOrError <object> .CreateError(ex.Message)); } }