private void ResolveVariableValue(object sender, ResolveVariableValueEventArgs e) { // resolve variable from formulas model. var recursiveFormula = (from p in _model.Formulas where p.Name == e.VariableName select p).SingleOrDefault(); if (recursiveFormula != null) { // this variable is a formula. Recurvsively execute this formula. e.VariableValue = FormulaExpressionContext.Evaluate(this, ref _context, ref recursiveFormula, ref _parameters, OnQuestion, ref _model).Value.Infer(); //var context = new FormulaExpressionContext(ref _model, ref _parameters, recursiveFormula, OnQuestion); //e.VariableValue = context.Evaluate().Value.Infer(); } else { if (_parameters.Find(p => p.Name == e.VariableName) != null) { return; } // this variable is an input variable. Provide a question to the client for an answer. if (OnQuestion == null) { throw new Exception($"In order to evaluate variable '${e.VariableName}', you need to provide a delegate callback to the client for providing an answer"); } OnQuestion(this, new QuestionArgs("", new ParametersCollection() { new Parameter(e.VariableName, null, TypeInference.Infer(e.VariableType).Type, ref _model) })); } }
public void EvaluateFormulaWithoutQA(ref IParametersCollection parameters, string formula) { void callback(FormulaExpressionContext sender, QuestionArgs args) { throw new Exception($"Can't resolve formula {formula} without answering question {args.Parameters[0].Name} first."); }; var context = new FormulaExpressionContext(ref _model, ref parameters, GetFormula(formula), callback, this); context.Evaluate(); }
public Table Match(string tableName, ref IParametersCollection parameters) { int i = 0; int nonSituationalTables = 0; List <Table> matchedTables = new List <Table>(); var tables = _model.GetTablesByName(tableName); ISituation currentSituation = null; void callback(FormulaExpressionContext sender, QuestionArgs args) { throw new Exception($"{tableName} contains situational expression {currentSituation.Expression} that could not be resolved without answering a question: {args.Parameters[0].Name}. Situational tables should only contain resolveable parameters."); }; foreach (var table in _model.GetTablesByName(tableName)) { i++; if (!table.IsSituational) { nonSituationalTables++; if (nonSituationalTables > 1) { throw new Exception($"found multiple tables found for {tableName} that are non situational. Only one table is allowd to be non situational."); } if (!table.IsSituational && i != tables.Count()) { throw new Exception($"Default table {tableName} should be specified after all situational tables."); } matchedTables.Add(table); continue; } // evaluate situations for inclusivity (meaning one of the expressions should evaluate to true. foreach (var situation in table.Situations) { currentSituation = situation; var formula = new Formula(table.DebugInfo, "__temp__", new List <Function>() { new Function(table.DebugInfo, situation.Expression) }); var context = new FormulaExpressionContext(ref _model, ref parameters, formula, callback, this); var result = context.Evaluate(); if (result.Type != TypeEnum.Boolean) { throw new Exception($"{tableName} contains situational expression {currentSituation.Expression} and should evaluate to a boolean, instead it resolved to a: {result.TypeAsString}."); } if ((bool)result.Value) { matchedTables.Add(table); } } } if (matchedTables.Count == 0) { throw new Exception($"No table matches for {tableName}."); } if (matchedTables.Count > 1 && nonSituationalTables == 0) { throw new Exception($"Ambigous situation evaluation on tables named {tableName}."); } return(matchedTables[0]); }
private void ExecuteStep(ref IExecutionResult executionResult, ref IParametersCollection parameters, IStep step) { var questions = new ParametersCollection(); if (executionResult is null) { throw new ArgumentNullException(nameof(executionResult)); } if (parameters is null) { throw new ArgumentNullException(nameof(parameters)); } executionResult.Stacktrace.Add(new FlowExecutionItem(step, null)); // Prescedence is (correct order of evaluation) // #1 evaluate step variable (value Question) or evaluate situation (choice Question) // #2 evaluate formula // #3 evaluate break (recht) // Evaluate step variable if available // Ask a question for a direct input value from the client, if the parameter is unknown. // Note: in inference we assume the input value is of type double. // When used in combination with "recht", do not advance to the next step until recht is evaluated. // When used in combination with "formule", do not advance to the next step until the formule is evaluated. if (!string.IsNullOrEmpty(step.Value) && parameters.GetParameter(step.Value) == null) { if (QuestionCallback == null) { throw new Exception($"In order to evaluate step variable {step.Value}, you need to provide a delegate callback to the client for providing an answer"); } questions.UpSert(new Parameter(step.Value, 0, TypeEnum.Double, ref _model)); QuestionCallback(null, new QuestionArgs("", questions)); // step variable has to be formulated as an input parameter by the client. throw new UnresolvedException($"Can't evaluate step variable {step.Value}."); } if (step.Choices != null) { bool answered = false; foreach (var choice in step.Choices) { var situation = parameters.GetParameter(choice.Situation); if (situation == null) { questions.UpSert(new Parameter(choice.Situation, false, TypeEnum.Boolean, ref _model)); } else { if ((bool)situation.Value == true) { answered = true; } } } if (!answered) { QuestionCallback(null, new QuestionArgs("", questions)); throw new UnresolvedException($"Choices {string.Join(',', step.Choices.Select(p => p.Situation))} can not evaluate further, before these are answered by the client."); } } if (!string.IsNullOrEmpty(step.Formula)) { var context = new FormulaExpressionContext(ref _model, ref parameters, GetFormula(step.Formula), QuestionCallback, this); context.Evaluate(); } // Evaluate recht if available. if (step.Break != null && !string.IsNullOrEmpty(step.Break.Expression)) { var breakContext = new FormulaExpressionContext( ref _model, ref parameters, new Formula(step.DebugInfo, "recht", new List <Function>() { new Function(step.DebugInfo, step.Break.Expression) }), QuestionCallback, this); breakContext.Evaluate(); } CheckForStopExecution(parameters, executionResult); }
private bool EvaluateSituation(ref IParametersCollection parameters, IStep step, out IStep stepActiveSituation) { if (parameters is null) { throw new ArgumentNullException(nameof(parameters)); } stepActiveSituation = step.Clone(); var match = false; // evaluate if the situation (condition) is appropiate, otherwise skip it. // not in input parameters. Evaluate the var parameter = parameters.GetParameter(step.Situation); if (step.Situation.Contains(',')) { foreach (var inclusiveSituation in step.Situation.Split(',') .Select(x => x.Trim()) .Where(x => !string.IsNullOrWhiteSpace(x)) .ToArray()) { parameter = parameters.GetParameter(inclusiveSituation); if (parameter != null && parameter.Type == TypeEnum.Boolean) { if ((bool)parameter.Value == true) { stepActiveSituation = step.Clone(); stepActiveSituation.Situation = inclusiveSituation; stepActiveSituation.SemanticKey = string.Join('.', stepActiveSituation.SemanticKey.Split('.').Take(3).ToArray()) + "." + inclusiveSituation; // this is a hack, please refactor. } // seach in content nodes for this parameter in typenum steps //var s = ContentNodes.FindAll(p => p.Parameter.Type == TypeEnum.Step); return(true); } } throw new StepException($"Can't resolve any of the inclusive situations ${step.Situation}. One of these parameters must exist and be of boolean type.", step); } if (parameter == null) { // resolve parameter value from named formula. var formula = GetFormula(step.Situation); if (formula == null) { throw new StepException($"can't resolve parameter '${parameter.Name}' to formula", step); } // execute the formula which adds the outcome to parameters. var context = new FormulaExpressionContext(ref _model, ref parameters, formula, QuestionCallback, this); var result = context.Evaluate(); //var result = Execute(ref context, ref formula, ref parameters, ref executionResult); if (result.Value.GetType() != typeof(bool)) { throw new StepException($"Expected situation to evalute to boolean value in worflow step {step.Name} {step.Description} but got value: {result.Value} of type {result.Value.GetType().Name} instead.", step); } match = (bool)result.Value; } else { if ((bool)parameter.Value.Infer()) { // execute this step. match = true; } } return(match); }
private static Parameter Evaluate(FormulaExpressionContext caller, ref ExpressionContext context, ref Formula formula, ref ParametersCollection parameters1, QuestionDelegate onQuestion, ref Model.Model model) { if (parameters1 is null) { throw new ArgumentNullException(nameof(parameters1)); } IDynamicExpression e = null; if (!formula.IsSituational) { try { e = context.CompileDynamic(formula.Functions[0].Expression); var result = e.Evaluate().Infer(); Parameter parameter; parameter = new Parameter(formula.Name, result.Infer(), null, ref model); parameter.IsCalculated = true; parameters1.Add(parameter); if (context.Variables.ContainsKey(parameter.Name)) { context.Variables.Remove(parameter.Name); } context.Variables.Add(parameter.Name, parameter.Value.Infer()); return(parameter); } catch (ExpressionCompileException) { // Function can not evaluate further, before a Question/Answer sequence is fullfilled by the client. throw new UnresolvedException($"Function {formula.Functions[0].Expression} can not evaluate further, before a Question/Answer sequence is fullfilled by the client."); } } else { foreach (var function in formula.Functions) { foreach (var item in parameters1) { if (item.Name == function.Situation && (bool)item.Value == true) { try { e = context.CompileDynamic(function.Expression); var parameter = new Parameter(formula.Name, e.Evaluate().Infer(), null, ref model); parameter.IsCalculated = true; parameters1.Add(parameter); if (context.Variables.ContainsKey(parameter.Name)) { context.Variables.Remove(parameter.Name); } context.Variables.Add(parameter.Name, parameter.Value.Infer()); return(parameter); } catch (ExpressionCompileException) { // Function can not evaluate further, before a Question/Answer sequence is fullfilled by the client. throw new UnresolvedException($"Function {function.Expression} can not evaluate further, before a Question/Answer sequence is fullfilled by the client."); } } } } StringBuilder situations = new StringBuilder(); var parameters = new ParametersCollection(); foreach (var function in formula.Functions) { situations.Append(function.Situation + ","); var parameter = new Parameter(function.Situation, TypeInference.Infer(function).Type, null, ref model); parameters.Add(parameter); } if (onQuestion == null) { throw new Exception($"In order to evaluate variable one of the following situations: {situations.ToString().Trim(',')}, you need to provide a delegate callback to the client for providing an answer"); } onQuestion(caller, new QuestionArgs("", parameters)); // situation has to be formulated as an input parameter by the client. throw new UnresolvedException($"Can't evaluate formula {formula.Name} for situation. Please specify one of the following situations: {situations.ToString().Trim(',')}."); } }