Пример #1
0
        public Result Parse(string usage)
        {
            var matchdata = new MatchData();
            var stream    = new MemoryStream(Encoding.Default.GetBytes(usage), false);

            foreach (var g in grammar)
            {
                if (false == g.Read(stream, matchdata))
                {
                    return(new NoMatch(usage, MatchResult.NoMatch));
                }
            }

            //If we get here, we've satisfied the grammar of the template
            if (stream.ReadByte() >= 0)                          //There's still unread bytes in this stream but no more grammar, we don't have a perfect match
            {
                return(new NoMatch(usage, MatchResult.NoMatch)); //TODO: If no other templates match, this might be our best bet - store closest match and return that.
            }

            //Success!  Now we just have to build the full IngredientUsage from the collected data
            NlpTracer.Trace(TraceLevel.Info, "Usage \"{0}\" matches the grammar \"{1}\"", usage, this);
            var ret = Result.BuildResult(this, usage, matchdata);

            return(ret);
        }
Пример #2
0
        private static bool ProcessAnomalousMatch(string input, MatchData matchdata, IngredientUsage result, UnitType parsedType,
                                                  out Result anomalousMatch)
        {
            NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Checking for form matching prep note: {0}", matchdata.Preps);

            IngredientForm form;

            if (FormSynonyms.TryGetFormForPrep(matchdata.Preps, matchdata.Ingredient, true, out form))
            {
                result.Form = form;

                if (parsedType == Unit.GetConvType(result.Form.FormUnitType))
                {
                    NlpTracer.Trace(TraceLevel.Debug,
                                    "[BuildResult] SUCCESS: Found matching volumetric form, allowing prep to form fall-through.");
                    result.PrepNote = matchdata.Preps.ToString();
                    {
                        anomalousMatch = new AnomalousMatch(input, AnomalousResult.Fallthrough, result);
                        return(true);
                    }
                }
                else
                {
                    NlpTracer.Trace(TraceLevel.Debug,
                                    "[BuildResult] Found matching form, but form is not compatible with volumetric usage.");
                }
            }
            anomalousMatch = null;
            return(false);
        }
Пример #3
0
        public Result Parse(string input)
        {
            ReplaceAccents(ref input);
            var normalizedInput = reWhitespace.Replace(input, " ").ToLower(); //Replace 2 or more spaces with a single space since whitespace doesn't really matter

            //Loop through all loaded templates looking for a match - return that match, or return null if unknown
            var bestResult = MatchResult.NoMatch;

            foreach (var t in templates)
            {
                var result = t.Parse(normalizedInput);
                if (result is Match)
                {
                    Stats[t]++;
                    return(result);
                }
                else
                {
                    if (result.Status > bestResult)
                    {
                        bestResult = result.Status;
                    }
                }
            }

            NlpTracer.Trace(TraceLevel.Info, "Could not find match for usage: {0}", input);
            var ret = new NoMatch(input, bestResult);

            if (this.OnNoMatch != null)
            {
                OnNoMatch(ret, input);
            }

            return(ret); //TODO: Save best match to get error code
        }
Пример #4
0
        private static bool AutoFormConvert(string input, MatchData matchdata, IngredientUsage result, UnitType parsedType,
                                            DefaultPairings pairings, out Result autoConvertResult)
        {
            NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Form and unit incompatible - attempting to auto-convert form {0}",
                            result.Form);
            var formType = Unit.GetConvType(result.Form.FormUnitType);

            if (parsedType == UnitType.Weight && formType == UnitType.Volume && pairings.HasWeight)
            //Something like 3oz shredded cheddar cheese, we need to use a weight form and set prep note
            {
                NlpTracer.Trace(TraceLevel.Debug,
                                "[BuildResult] SUCCESS: Converting to default weight pairing, and setting prep note to: {0}",
                                result.Form.FormDisplayName);
                result.PrepNote = result.Form.FormDisplayName;
                result.Form     = pairings.Weight;
                {
                    autoConvertResult = new AnomalousMatch(input, AnomalousResult.AutoConvert, result);
                    return(true);
                }
            }
            else if (parsedType == UnitType.Unit && formType == UnitType.Volume) //Something like 3 mashed bananas
            {
                if (pairings.HasUnit && (matchdata.Unit == null || String.IsNullOrEmpty(matchdata.Unit.Name)))
                //No custom unit, just use default pairing
                {
                    NlpTracer.Trace(TraceLevel.Debug,
                                    "[BuildResult] SUCCESS: Converting to default unit pairing, and setting prep note to: {0}",
                                    result.Form.FormDisplayName);
                    result.PrepNote = result.Form.FormDisplayName;
                    result.Form     = pairings.Unit;
                    {
                        autoConvertResult = new AnomalousMatch(input, AnomalousResult.AutoConvert, result);
                        return(true);
                    }
                }

                if (matchdata.Unit != null && false == String.IsNullOrEmpty(matchdata.Unit.Name)) //We have a custom unit
                {
                    IngredientForm form;
                    NlpTracer.Trace(TraceLevel.Debug,
                                    "[BuildResult] Attempting to convert volumetric usage to unit form for custom unit: {0}",
                                    matchdata.Unit.Name);
                    if (UnitSynonyms.TryGetFormForIngredient(matchdata.Unit.Name, matchdata.Ingredient.Id, out form))
                    {
                        NlpTracer.Trace(TraceLevel.Debug,
                                        "[BuildResult] SUCCESS: Converting to custom unit pairing, and setting prep note to: {0}",
                                        result.Form.FormDisplayName);
                        result.PrepNote = result.Form.FormDisplayName;
                        result.Form     = form;
                        {
                            autoConvertResult = new AnomalousMatch(input, AnomalousResult.AutoConvert, result);
                            return(true);
                        }
                    }
                }
            }
            autoConvertResult = null;
            return(false);
        }
Пример #5
0
        public void LoadTemplates(params Template[] templates)
        {
            this.templates = new List <Template>(templates.Length);
            Stats          = new TemplateStatistics();

            foreach (var t in templates)
            {
                NlpTracer.Trace(TraceLevel.Debug, "Loaded Template: {0}", t);
                this.templates.Add(t);
                Stats.RecordTemplate(t);
            }
        }
Пример #6
0
        private static void LoadDefaultForm(MatchData matchdata, IngredientUsage result, DefaultPairings pairings)
        {
            if (matchdata.Unit == null || matchdata.Unit.Unit == Units.Unit)
            //TODO: Is second part necessary? Only Units.Unit would be custom form types, and we'd have errored out already if that didn't match
            {
                result.Form = pairings.Unit;
                NlpTracer.ConditionalTrace(pairings.HasUnit, TraceLevel.Debug,
                                           "[BuildResult] Linking to default Unit paired form {0}", pairings.Unit);
            }
            else
            {
                switch (Unit.GetConvType(matchdata.Unit.Unit))
                {
                case UnitType.Volume:
                    result.Form = pairings.Volume;
                    NlpTracer.ConditionalTrace(pairings.HasVolume, TraceLevel.Debug,
                                               "[BuildResult] Linking to default paired Volume form {0}", pairings.Volume);
                    break;

                case UnitType.Weight:
                    result.Form = pairings.Weight;
                    NlpTracer.ConditionalTrace(pairings.HasWeight, TraceLevel.Debug,
                                               "[BuildResult] Linking to default paired Weight form {0}", pairings.Weight);
                    break;
                }
            }

            if (result.Form == null && result.Amount.Unit == Units.Ounce && pairings.HasVolume)
            //Try as FluidOunces because so many recipes use oz when they mean fl oz
            {
                result.Form        = pairings.Volume;
                result.Amount.Unit = Units.FluidOunce;
                NlpTracer.Trace(TraceLevel.Debug,
                                "[BuildResult] Interpretting reference to Ounces as Fluid Ounces and linking to volumetric form {0}",
                                pairings.Volume);
            }

            if (result.Form == null)
            {
                NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Could not find any default pairing for the unit type: {0}",
                                result.Amount.Unit);
            }
        }
Пример #7
0
        private static bool ProcessCustimUnitNode(string input, MatchData matchdata, IngredientUsage result, out Result noMatch)
        {
            IngredientForm form;

            if (UnitSynonyms.TryGetFormForIngredient(matchdata.Unit.Name, matchdata.Ingredient.Id, out form))
            {
                NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Based on unit name {0}, linking to form id {1}",
                                matchdata.Unit.Name, form.FormId);
                result.Form = form;
            }
            else
            {
                NlpTracer.Trace(TraceLevel.Debug,
                                "[BuildResult] ERROR: Unable to find link between unit '{0}' and ingredient '{1}'.", matchdata.Unit.Name,
                                result.Ingredient.Name);
                {
                    noMatch = new NoMatch(input, MatchResult.UnknownUnit);
                    return(true);
                }
            }
            noMatch = null;
            return(false);
        }
Пример #8
0
        private static bool ProcessMatchDataNullForm(string input, MatchData matchdata, IngredientUsage result,
                                                     out Result buildResultNullForm)
        {
            IngredientForm form;

            if (FormSynonyms.TryGetFormForIngredient(matchdata.Form.FormName, matchdata.Ingredient.Id, out form))
            {
                NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Based on reference to form {0}, linking to form id {1}",
                                matchdata.Form.FormName, form.FormId);
                result.Form = form;
            }
            else
            {
                NlpTracer.Trace(TraceLevel.Debug,
                                "[BuildResult] ERROR: Unable to find link between form '{0}' and ingredient '{1}.", matchdata.Form.FormName,
                                result.Ingredient.Name);
                {
                    buildResultNullForm = new NoMatch(input, MatchResult.UnknownForm);
                    return(true);
                }
            }
            buildResultNullForm = null;
            return(false);
        }
Пример #9
0
        /// <summary>Assembles a Result baesd on NLP match data for a given template.</summary>
        /// <param name="template">A passing NLP template that yieled match data</param>
        /// <param name="input">The original input string that was parsed</param>
        /// <param name="matchdata">Match data generated from the specified NLP template</param>
        /// <returns>Result describing the generated KitchenPC IngredientUsage or error code if usage could not be generated</returns>
        public static Result BuildResult(Template template, string input, MatchData matchdata)
        {
            var result  = new IngredientUsage(); //Use this to hold partial matching data, but throw away if not a complete match.
            var ingName = (matchdata.Ingredient.Parent == null) ? matchdata.Ingredient.IngredientName : matchdata.Ingredient.Parent.IngredientName;

            result.Ingredient = new Ingredient(matchdata.Ingredient.Id, ingName);
            result.Ingredient.ConversionType = matchdata.Ingredient.ConversionType;
            result.Ingredient.UnitWeight     = matchdata.Ingredient.UnitWeight;
            result.Amount   = matchdata.Amount;
            result.PrepNote = matchdata.Preps.HasValue ? matchdata.Preps.ToString() : template.DefaultPrep;
            var pairings = matchdata.Ingredient.Pairings;

            NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Ingredient: {0}", matchdata.Ingredient.IngredientName);
            if (matchdata.Ingredient.Parent != null)
            {
                NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Re-Link to Root Ingredient: {0}", matchdata.Ingredient.Parent.IngredientName);
            }

            if (template.AllowPartial && matchdata.Amount == null) //No amount, any forms are now irrelevant
            {
                return(new PartialMatch(input, result.Ingredient, result.PrepNote));
            }

            //If we parsed a custom unit (heads of lettuce), check if there's a mapping for that unit name to the ingredient and use that form
            if (matchdata.Unit is CustomUnitNode)
            {
                Result noMatch;
                bool   hasReturned = ProcessCustimUnitNode(input, matchdata, result, out noMatch);
                if (hasReturned)
                {
                    return(noMatch);
                }
            }
            else
            {
                NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] No custom unit found, so cannot get form based on unit.");
            }

            //If we parsed a form alias (shredded), lookup the FormID from the formname/ingredient map (will override unit alias)
            if (matchdata.Form != null)
            {
                Result MatchDataFormResult;
                bool   hasReturned = ProcessMatchDataNullForm(input, matchdata, result, out MatchDataFormResult);
                if (hasReturned)
                {
                    return(MatchDataFormResult);
                }
            }
            else
            {
                NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] No known form found, so cannot get form based on form synonym.");
            }

            if (result.Form == null) //Load default form for parsed unit type
            {
                LoadDefaultForm(matchdata, result, pairings);
            }

            //If we've loaded a form type and they're compatible, return match
            var parsedType = Unit.GetConvType(result.Amount.Unit);

            if (result.Form != null && parsedType == Unit.GetConvType(result.Form.FormUnitType))
            {
                NlpTracer.Trace(TraceLevel.Info, "[BuildResult] SUCCESS: Linked form is compatible with usage reference.");
                return(new Match(input, result));
            }

            // **************************
            // *** ANOMALOUS PARSING ****
            // **************************
            NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Running anomalous parsing.");

            // Prep to form fall-through: Allow prep to clarify form with volumetric usages if no default pairing is known, eg: 3 cups apples, chopped --> apples (chopped) : 3 cups
            // TODO: If matchdata has multiple prep notes, we either need to only parse the user entered one or avoid duplicate matches
            if (parsedType == UnitType.Volume && matchdata.Preps.HasValue)
            {
                Result anomalousMatch;
                bool   hasReturned = ProcessAnomalousMatch(input, matchdata, result, parsedType, out anomalousMatch);
                if (hasReturned)
                {
                    return(anomalousMatch);
                }
            }
            else
            {
                NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Could not clarify form through prep note, since unit type is not volumetric or there is no prep note.");
            }

            // Auto Form Conversion: If we can make a valid assumption about a form even if unit type is incompatible, we can convert to another form, eg: 5oz shredded cheese --> cheese: 5oz (shredded)
            if (result.Form != null)
            {
                Result AutoConversionResult;
                bool   wasConverted = AutoFormConvert(input, matchdata, result, parsedType, pairings,
                                                      out AutoConversionResult);
                if (wasConverted)
                {
                    return(AutoConversionResult);
                }
            }
            else
            {
                NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Could not auto-convert form since there is no form to convert.");
            }

            //Error out
            if (result.Form == null) //Still have not found a form, we give up now
            {
                NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] ERROR: Anomalous parsing could still not find a form for this usage.");
                return(new NoMatch(input, MatchResult.NoForm)); //No default form pairings, so return NoForm
            }

            NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] ERROR: Anomalous parsing could not fix form/unit incompatibility.");
            return(new NoMatch(input, MatchResult.IncompatibleForm));
        }
Пример #10
0
        /// <summary>Assembles a Result baesd on NLP match data for a given template.</summary>
        /// <param name="template">A passing NLP template that yieled match data</param>
        /// <param name="input">The original input string that was parsed</param>
        /// <param name="matchdata">Match data generated from the specified NLP template</param>
        /// <returns>Result describing the generated KitchenPC IngredientUsage or error code if usage could not be generated</returns>
        public static Result BuildResult(Template template, string input, MatchData matchdata)
        {
            var result  = new IngredientUsage(); //Use this to hold partial matching data, but throw away if not a complete match.
            var ingName = (matchdata.Ingredient.Parent == null) ? matchdata.Ingredient.IngredientName : matchdata.Ingredient.Parent.IngredientName;

            result.Ingredient = new Ingredient(matchdata.Ingredient.Id, ingName);
            result.Ingredient.ConversionType = matchdata.Ingredient.ConversionType;
            result.Ingredient.UnitWeight     = matchdata.Ingredient.UnitWeight;
            result.Amount   = matchdata.Amount;
            result.PrepNote = matchdata.Preps.HasValue ? matchdata.Preps.ToString() : template.DefaultPrep;
            var pairings = matchdata.Ingredient.Pairings;

            NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Ingredient: {0}", matchdata.Ingredient.IngredientName);
            if (matchdata.Ingredient.Parent != null)
            {
                NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Re-Link to Root Ingredient: {0}", matchdata.Ingredient.Parent.IngredientName);
            }

            if (template.AllowPartial && matchdata.Amount == null) //No amount, any forms are now irrelevant
            {
                return(new PartialMatch(input, result.Ingredient, result.PrepNote));
            }

            //If we parsed a custom unit (heads of lettuce), check if there's a mapping for that unit name to the ingredient and use that form
            if (matchdata.Unit is CustomUnitNode)
            {
                IngredientForm form;
                if (UnitSynonyms.TryGetFormForIngredient(matchdata.Unit.Name, matchdata.Ingredient.Id, out form))
                {
                    NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Based on unit name {0}, linking to form id {1}", matchdata.Unit.Name, form.FormId);
                    result.Form = form;
                }
                else
                {
                    NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] ERROR: Unable to find link between unit '{0}' and ingredient '{1}'.", matchdata.Unit.Name, result.Ingredient.Name);
                    return(new NoMatch(input, MatchResult.UnknownUnit)); //User specified a custom form that is not in any way linked to this ingredient, this is an error condition
                }
            }
            else
            {
                NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] No custom unit found, so cannot get form based on unit.");
            }

            //If we parsed a form alias (shredded), lookup the FormID from the formname/ingredient map (will override unit alias)
            if (matchdata.Form != null)
            {
                IngredientForm form;
                if (FormSynonyms.TryGetFormForIngredient(matchdata.Form.FormName, matchdata.Ingredient.Id, out form))
                {
                    NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Based on reference to form {0}, linking to form id {1}", matchdata.Form.FormName, form.FormId);
                    result.Form = form;
                }
                else
                {
                    NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] ERROR: Unable to find link between form '{0}' and ingredient '{1}.", matchdata.Form.FormName, result.Ingredient.Name);
                    return(new NoMatch(input, MatchResult.UnknownForm)); //User specified a form that is not in any way linked to this ingredient, this is an error condition
                }
            }
            else
            {
                NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] No known form found, so cannot get form based on form synonym.");
            }

            if (result.Form == null)                                             //Load default form for parsed unit type
            {
                if (matchdata.Unit == null || matchdata.Unit.Unit == Units.Unit) //TODO: Is second part necessary? Only Units.Unit would be custom form types, and we'd have errored out already if that didn't match
                {
                    result.Form = pairings.Unit;
                    NlpTracer.ConditionalTrace(pairings.HasUnit, TraceLevel.Debug, "[BuildResult] Linking to default Unit paired form {0}", pairings.Unit);
                }
                else
                {
                    switch (Unit.GetConvType(matchdata.Unit.Unit))
                    {
                    case UnitType.Volume:
                        result.Form = pairings.Volume;
                        NlpTracer.ConditionalTrace(pairings.HasVolume, TraceLevel.Debug, "[BuildResult] Linking to default paired Volume form {0}", pairings.Volume);
                        break;

                    case UnitType.Weight:
                        result.Form = pairings.Weight;
                        NlpTracer.ConditionalTrace(pairings.HasWeight, TraceLevel.Debug, "[BuildResult] Linking to default paired Weight form {0}", pairings.Weight);
                        break;
                    }
                }

                if (result.Form == null && result.Amount.Unit == Units.Ounce && pairings.HasVolume) //Try as FluidOunces because so many recipes use oz when they mean fl oz
                {
                    result.Form        = pairings.Volume;
                    result.Amount.Unit = Units.FluidOunce;
                    NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Interpretting reference to Ounces as Fluid Ounces and linking to volumetric form {0}", pairings.Volume);
                }

                if (result.Form == null)
                {
                    NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Could not find any default pairing for the unit type: {0}", result.Amount.Unit);
                }
            }

            //If we've loaded a form type and they're compatible, return match
            var parsedType = Unit.GetConvType(result.Amount.Unit);

            if (result.Form != null && parsedType == Unit.GetConvType(result.Form.FormUnitType))
            {
                NlpTracer.Trace(TraceLevel.Info, "[BuildResult] SUCCESS: Linked form is compatible with usage reference.");
                return(new Match(input, result));
            }

            // **************************
            // *** ANOMALOUS PARSING ****
            // **************************
            NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Running anomalous parsing.");

            // Prep to form fall-through: Allow prep to clarify form with volumetric usages if no default pairing is known, eg: 3 cups apples, chopped --> apples (chopped) : 3 cups
            // TODO: If matchdata has multiple prep notes, we either need to only parse the user entered one or avoid duplicate matches
            if (parsedType == UnitType.Volume && matchdata.Preps.HasValue)
            {
                NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Checking for form matching prep note: {0}", matchdata.Preps);

                IngredientForm form;
                if (FormSynonyms.TryGetFormForPrep(matchdata.Preps, matchdata.Ingredient, true, out form))
                {
                    result.Form = form;

                    if (parsedType == Unit.GetConvType(result.Form.FormUnitType))
                    {
                        NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] SUCCESS: Found matching volumetric form, allowing prep to form fall-through.");
                        result.PrepNote = matchdata.Preps.ToString();
                        return(new AnomalousMatch(input, AnomalousResult.Fallthrough, result));
                    }
                    else
                    {
                        NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Found matching form, but form is not compatible with volumetric usage.");
                    }
                }
            }
            else
            {
                NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Could not clarify form through prep note, since unit type is not volumetric or there is no prep note.");
            }

            // Auto Form Conversion: If we can make a valid assumption about a form even if unit type is incompatible, we can convert to another form, eg: 5oz shredded cheese --> cheese: 5oz (shredded)
            if (result.Form != null)
            {
                NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Form and unit incompatible - attempting to auto-convert form {0}", result.Form);
                var formType = Unit.GetConvType(result.Form.FormUnitType);

                if (parsedType == UnitType.Weight && formType == UnitType.Volume && pairings.HasWeight) //Something like 3oz shredded cheddar cheese, we need to use a weight form and set prep note
                {
                    NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] SUCCESS: Converting to default weight pairing, and setting prep note to: {0}", result.Form.FormDisplayName);
                    result.PrepNote = result.Form.FormDisplayName;
                    result.Form     = pairings.Weight;
                    return(new AnomalousMatch(input, AnomalousResult.AutoConvert, result));
                }
                else if (parsedType == UnitType.Unit && formType == UnitType.Volume)                               //Something like 3 mashed bananas
                {
                    if (pairings.HasUnit && (matchdata.Unit == null || String.IsNullOrEmpty(matchdata.Unit.Name))) //No custom unit, just use default pairing
                    {
                        NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] SUCCESS: Converting to default unit pairing, and setting prep note to: {0}", result.Form.FormDisplayName);
                        result.PrepNote = result.Form.FormDisplayName;
                        result.Form     = pairings.Unit;
                        return(new AnomalousMatch(input, AnomalousResult.AutoConvert, result));
                    }

                    if (matchdata.Unit != null && false == String.IsNullOrEmpty(matchdata.Unit.Name)) //We have a custom unit
                    {
                        IngredientForm form;
                        NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Attempting to convert volumetric usage to unit form for custom unit: {0}", matchdata.Unit.Name);
                        if (UnitSynonyms.TryGetFormForIngredient(matchdata.Unit.Name, matchdata.Ingredient.Id, out form))
                        {
                            NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] SUCCESS: Converting to custom unit pairing, and setting prep note to: {0}", result.Form.FormDisplayName);
                            result.PrepNote = result.Form.FormDisplayName;
                            result.Form     = form;
                            return(new AnomalousMatch(input, AnomalousResult.AutoConvert, result));
                        }
                    }
                }
            }
            else
            {
                NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] Could not auto-convert form since there is no form to convert.");
            }

            //Error out
            if (result.Form == null) //Still have not found a form, we give up now
            {
                NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] ERROR: Anomalous parsing could still not find a form for this usage.");
                return(new NoMatch(input, MatchResult.NoForm)); //No default form pairings, so return NoForm
            }

            NlpTracer.Trace(TraceLevel.Debug, "[BuildResult] ERROR: Anomalous parsing could not fix form/unit incompatibility.");
            return(new NoMatch(input, MatchResult.IncompatibleForm));
        }