/// <summary> /// Implements TryParseDefinition. /// </summary> protected override bool TryParseDefinitionCore(ServiceDefinitionText source, out ServiceInfo?service, out IReadOnlyList <ServiceDefinitionError> errors) { var errorList = new List <ServiceDefinitionError>(); var definitionLines = new List <string>(); var remarksSectionsByName = new Dictionary <string, FsdRemarksSection>(StringComparer.OrdinalIgnoreCase); var interleavedRemarksSections = new List <FsdRemarksSection>(); if (!s_interleavedMarkdown.IsMatch(source.Text)) { ReadRemarksAfterDefinition(source, definitionLines, remarksSectionsByName, errorList); } else { ReadInterleavedRemarks(source, definitionLines, interleavedRemarksSections); } source = new ServiceDefinitionText(source.Name, string.Join("\n", definitionLines)); service = null; try { service = FsdParsers.ParseDefinition(source, remarksSectionsByName); errorList.AddRange(service.GetValidationErrors()); // check for unused remarks sections foreach (var remarksSectionPair in remarksSectionsByName) { var sectionName = remarksSectionPair.Key; if (service.Name != sectionName && service.FindMember(sectionName) == null) { errorList.Add(new ServiceDefinitionError($"Unused remarks heading: {sectionName}", remarksSectionPair.Value.Position)); } } // check for interleaved remarks sections foreach (var remarksSection in interleavedRemarksSections) { var remarksLineNumber = remarksSection.Position.LineNumber; if (remarksLineNumber > service.GetPart(ServicePartKind.Name) !.Position.LineNumber && remarksLineNumber < service.GetPart(ServicePartKind.End) !.Position.LineNumber) { ServiceMemberInfo targetMember = service; var targetLineNumber = 0; foreach (var member in service.Members) { var memberLineNumber = member.GetPart(ServicePartKind.Name) !.Position.LineNumber; if (remarksLineNumber > memberLineNumber && memberLineNumber > targetLineNumber) { targetMember = member; targetLineNumber = memberLineNumber; } } if (targetMember.Remarks.Count == 0) { targetMember.Remarks = remarksSection.Lines; } else { targetMember.Remarks = targetMember.Remarks.Concat(new[] { "" }).Concat(remarksSection.Lines).ToList(); } } } } catch (ParseException exception) { var expectation = exception .Result .GetNamedFailures() .Distinct() .GroupBy(x => x.Position) .Select(x => new { LineColumn = x.Key.GetLineColumn(), Names = x.Select(y => y.Name) }) .OrderByDescending(x => x.LineColumn.LineNumber) .ThenByDescending(x => x.LineColumn.ColumnNumber) .First(); int GetExpectationNameRank(string name) => name == "')'" || name == "']'" || name == "'}'" || name == "';'" ? 1 : 2; errorList.Add(new ServiceDefinitionError( "expected " + string.Join(" or ", expectation.Names.Distinct().OrderBy(GetExpectationNameRank).ThenBy(x => x, StringComparer.Ordinal)), new ServiceDefinitionPosition(source.Name, expectation.LineColumn.LineNumber, expectation.LineColumn.ColumnNumber))); } errors = errorList; return(errorList.Count == 0); }
/// <summary> /// Parses an FSD file into a service definition. /// </summary> public ServiceInfo ParseDefinition(NamedText source) { IReadOnlyList <string> definitionLines = null; var remarksSections = new Dictionary <string, FsdRemarksSection>(StringComparer.OrdinalIgnoreCase); // read remarks after definition using (var reader = new StringReader(source.Text)) { string name = null; var lines = new List <string>(); int lineNumber = 0; int headingLineNumber = 0; while (true) { string line = reader.ReadLine(); lineNumber++; Match match = line == null ? null : s_markdownHeading.Match(line); if (match == null || match.Success) { if (name == null) { definitionLines = lines; } else { while (lines.Count != 0 && string.IsNullOrWhiteSpace(lines[0])) { lines.RemoveAt(0); } while (lines.Count != 0 && string.IsNullOrWhiteSpace(lines[lines.Count - 1])) { lines.RemoveAt(lines.Count - 1); } var position = new NamedTextPosition(source.Name, headingLineNumber, 1); if (remarksSections.ContainsKey(name)) { throw new ServiceDefinitionException("Duplicate remarks heading: " + name, position); } remarksSections.Add(name, new FsdRemarksSection(name, lines, position)); } if (match == null) { break; } name = line.Substring(match.Index + match.Length).Trim(); lines = new List <string>(); headingLineNumber = lineNumber; } else { lines.Add(line); } } } source = new NamedText(source.Name, string.Join("\n", definitionLines)); ServiceInfo service; try { service = FsdParsers.ParseDefinition(source, remarksSections); } catch (ParseException exception) { var expectation = exception .Result .GetNamedFailures() .Distinct() .GroupBy(x => x.Position) .Select(x => new { LineColumn = x.Key.GetLineColumn(), Names = x.Select(y => y.Name) }) .OrderByDescending(x => x.LineColumn.LineNumber) .ThenByDescending(x => x.LineColumn.ColumnNumber) .First(); throw new ServiceDefinitionException( "expected " + string.Join(" or ", expectation.Names.Distinct().OrderBy(GetExpectationNameRank).ThenBy(x => x, StringComparer.Ordinal)), new NamedTextPosition(source.Name, expectation.LineColumn.LineNumber, expectation.LineColumn.ColumnNumber), exception); } // check for unused remarks sections foreach (var remarksSection in remarksSections.Values) { string sectionName = remarksSection.Name; if (service.Name != sectionName && service.FindMember(sectionName) == null) { throw new ServiceDefinitionException($"Unused remarks heading: {sectionName}", remarksSection.Position); } } return(service); }