private List <ConceptSyntaxNode> ExtractConcepts(MultiDictionary <string, IConceptParser> conceptParsers) { var stopwatch = Stopwatch.StartNew(); var tokenizerResult = _tokenizer.GetTokens(); if (tokenizerResult.SyntaxError != null) { ExceptionsUtility.Rethrow(tokenizerResult.SyntaxError); } var tokenReader = new TokenReader(tokenizerResult.Tokens, 0); var newConcepts = new List <ConceptSyntaxNode>(); var context = new Stack <ConceptSyntaxNode>(); var warnings = new List <string>(); tokenReader.SkipEndOfFile(); while (!tokenReader.EndOfInput) { var parsed = ParseNextConcept(tokenReader, context, conceptParsers); newConcepts.Add(parsed.ConceptInfo); if (parsed.Warnings != null) { warnings.AddRange(parsed.Warnings); } UpdateContextForNextConcept(tokenReader, context, parsed.ConceptInfo); OnKeyword?.Invoke(tokenReader, null); if (context.Count == 0) { tokenReader.SkipEndOfFile(); } } _performanceLogger.Write(stopwatch, "ExtractConcepts (" + newConcepts.Count + " concepts)."); if (context.Count > 0) { var(dslScript, position) = tokenReader.GetPositionInScript(); throw new DslSyntaxException($"Expected \"}}\" to close concept \"{context.Peek()}\".", "RH0002", dslScript, position, 0, ReportPreviousConcept(context.Peek())); } foreach (string warning in warnings) { if (_syntax.Value.ExcessDotInKey == ExcessDotInKey.Ignore) { _logger.Trace(warning); } else { _logger.Warning(warning); } } if (_syntax.Value.ExcessDotInKey == ExcessDotInKey.Error && warnings.Any()) { throw new DslSyntaxException(warnings.First()); } return(newConcepts); }
private (ConceptSyntaxNode ConceptInfo, List <string> Warnings) ParseNextConcept(TokenReader tokenReader, Stack <ConceptSyntaxNode> context, MultiDictionary <string, IConceptParser> conceptParsers) { var errorReports = new List <Func <(string formattedError, string simpleError)> >(); List <Interpretation> possibleInterpretations = new List <Interpretation>(); var keywordReader = new TokenReader(tokenReader).ReadText(); // Peek, without changing the original tokenReader's position. var keyword = keywordReader.IsError ? null : keywordReader.Value; OnKeyword?.Invoke(tokenReader, keyword); if (keyword != null) { foreach (var conceptParser in conceptParsers.Get(keyword)) { TokenReader nextPosition = new TokenReader(tokenReader); var conceptInfoOrError = conceptParser.Parse(nextPosition, context, out var warnings); if (!conceptInfoOrError.IsError) { possibleInterpretations.Add(new Interpretation { Node = conceptInfoOrError.Value, NextPosition = nextPosition, Warnings = warnings }); } else if (!string.IsNullOrEmpty(conceptInfoOrError.Error)) // Empty error means that this parser is not for this keyword. { errorReports.Add(() => (string.Format("{0}: {1}\r\n{2}", conceptParser.GetType().Name, conceptInfoOrError.Error, tokenReader.ReportPosition()), conceptInfoOrError.Error)); } } } if (possibleInterpretations.Count == 0) { var(dslScript, position) = tokenReader.GetPositionInScript(); if (errorReports.Count > 0) { var errorReportValues = errorReports.Select(x => x.Invoke()).ToList(); var errorsReport = string.Join("\r\n", errorReportValues.Select(x => x.formattedError)).Limit(500, "..."); var simpleErrorsReport = string.Join("\n", errorReportValues.Select(x => x.simpleError)); var simpleMessage = $"Invalid parameters after keyword '{keyword}'. Possible causes: {simpleErrorsReport}"; var possibleCauses = $"Possible causes:\r\n{errorsReport}"; throw new DslSyntaxException(simpleMessage, "RH0003", dslScript, position, 0, possibleCauses); } else if (!string.IsNullOrEmpty(keyword)) { var simpleMessage = $"Unrecognized concept keyword '{keyword}'."; throw new DslSyntaxException(simpleMessage, "RH0004", dslScript, position, 0, null); } else { var simpleMessage = $"Invalid DSL script syntax."; throw new DslSyntaxException(simpleMessage, "RH0005", dslScript, position, 0, null); } } Disambiguate(possibleInterpretations); if (possibleInterpretations.Count > 1) { var interpretations = new List <string>(); for (int i = 0; i < possibleInterpretations.Count; i++) { interpretations.Add($"{i + 1}. {possibleInterpretations[i].Node.Concept.AssemblyQualifiedName}"); } var simpleMessage = $"Ambiguous syntax. There are multiple possible interpretations of keyword '{keyword}': {string.Join(", ", interpretations)}."; var(dslScript, position) = tokenReader.GetPositionInScript(); throw new DslSyntaxException(simpleMessage, "RH0006", dslScript, position, 0, null); } var parsedStatement = possibleInterpretations.Single(); tokenReader.CopyFrom(parsedStatement.NextPosition); return(parsedStatement.Node, parsedStatement.Warnings); }