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); }
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); }
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 }
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); }
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); } }
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); } }
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); }
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); }
/// <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)); }
/// <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)); }