/// <inheritdoc/>
        public bool TryParseIngredient(
            ParserContext context,
            IEnumerable <Template> templates,
            out ParseResult parseResult)
        {
            var fullMatches = FindFullMatches(context, templates);

            if (fullMatches.Any())
            {
                var bestMatchMetadata = _bestMatchHeuristic.Invoke(fullMatches);

                parseResult = new ParseResult()
                {
                    Details  = new ParseResult.IngredientDetails(),
                    Metadata = bestMatchMetadata
                };

                var tokenVisitor = new ParserTokenVisitor(parseResult);

                foreach (var token in parseResult.Metadata.Tokens)
                {
                    token.Accept(tokenVisitor);
                }

                return(true);
            }

            parseResult = null;

            return(false);
        }
        /// <inheritdoc/>
        public bool TryParseIngredient(
            ParserContext context,
            IEnumerable <Template> templates,
            out ParseResult parseResult)
        {
            var partialMatches = new List <ParseResult.ParseMetadata>();

            foreach (var template in templates)
            {
                context.Buffer.Reset();

                var result = template.TryReadTokens(context, out var tokens);

                switch (result)
                {
                case TemplateMatchResult.NoMatch:
                    // Always skip non-matches.
                    continue;

                case TemplateMatchResult.PartialMatch:
                    // Take a note of partial matches, so that we can find
                    // the best match when no full matches are found
                    partialMatches.Add(new ParseResult.ParseMetadata()
                    {
                        Template    = template,
                        MatchResult = TemplateMatchResult.PartialMatch,
                        Tokens      = tokens.ToList()
                    });

                    continue;

                case TemplateMatchResult.FullMatch:
                    // Stop on the first full match
                    parseResult = new ParseResult()
                    {
                        Details  = new ParseResult.IngredientDetails(),
                        Metadata = new ParseResult.ParseMetadata()
                        {
                            Template    = template,
                            MatchResult = TemplateMatchResult.FullMatch,
                            Tokens      = tokens.ToList()
                        }
                    };

                    VisitTokens(parseResult);

                    return(true);

                default:
                    throw new ArgumentOutOfRangeException(
                              $"Encountered unknown template match result: {result}");
                }
            }

            if (partialMatches.Any())
            {
                var bestMatchMetadata = _bestMatchHeuristic.Invoke(partialMatches);

                parseResult = new ParseResult()
                {
                    Details  = new ParseResult.IngredientDetails(),
                    Metadata = bestMatchMetadata
                };

                VisitTokens(parseResult);

                return(true);
            }

            parseResult = null;

            return(false);
        }