/// <summary> /// Create or update a named variable to a value. /// </summary> /// <param name="name">variable name. This can be used in other formulas.</param> /// <param name="value">constant value.</param> public void UpdateVariable(string name, FormulaValue value) { var x = value; if (Formulas.TryGetValue(name, out var fi)) { // Type should match? if (fi._type != x.Type) { throw new NotSupportedException($"Can't change '{name}''s type from {fi._type} to {x.Type}."); } fi._value = x; // Be sure to preserve used-by set. } else { Formulas[name] = new RecalcFormulaInfo { _value = x, _type = x.IRContext.ResultType }; } // Could trigger recalcs? Recalc(name); }
/// <summary> /// Create a formula that will be recalculated when its dependent values change. /// </summary> /// <param name="name">name of formula. This can be used in other formulas.</param> /// <param name="expr">expression.</param> /// <param name="onUpdate">Callback to fire when this value is updated.</param> public void SetFormula(string name, FormulaWithParameters expr, Action <string, FormulaValue> onUpdate) { if (Formulas.ContainsKey(name)) { throw new InvalidOperationException($"Can't change existing formula: {name}"); } var check = Check(expr._expression, expr._schema); check.ThrowOnErrors(); var binding = check._binding; // We can't have cycles because: // - formulas can only refer to already-defined values // - formulas can't be redefined. HashSet <string> dependsOn = check.TopLevelIdentifiers; var type = FormulaType.Build(binding.ResultType); var info = new RecalcFormulaInfo { _dependsOn = dependsOn, _type = type, _binding = binding, _onUpdate = onUpdate }; Formulas[name] = info; foreach (var x in dependsOn) { Formulas[x]._usedBy.Add(name); } Recalc(name); }