Add current input tracking and enhance debug logging in ExpressionConfigEntry

This commit is contained in:
2025-05-21 16:36:31 +02:00
parent 345f8c4565
commit 9583944c69

View File

@@ -10,6 +10,7 @@ namespace BanquetForCyka {
private Func<float, float> _compiledExpression; private Func<float, float> _compiledExpression;
private string _lastValidExpression; private string _lastValidExpression;
private bool _isValid = true; private bool _isValid = true;
private string _currentInput;
public string Value { public string Value {
get => _configEntry.Value; get => _configEntry.Value;
@@ -17,164 +18,221 @@ namespace BanquetForCyka {
if (value == _configEntry.Value) if (value == _configEntry.Value)
return; return;
_currentInput = value;
// Try to compile the expression // Try to compile the expression
try { try {
LogDebug($"Attempting to compile expression: '{value}'"); LogDebug($"=== Starting compilation of new expression ===", _debug);
var newExpr = ParseExpression(value); LogDebug($"Input expression: '{value}'", _debug);
var newExpr = ParseExpression(value, _debug);
_compiledExpression = newExpr; _compiledExpression = newExpr;
_lastValidExpression = value; _lastValidExpression = value;
_isValid = true; _isValid = true;
_configEntry.Value = value; LogDebug($"=== Expression compilation successful ===", _debug);
LogDebug($"Expression '{value}' compiled successfully");
} catch (Exception e) { } catch (Exception e) {
_isValid = false; _isValid = false;
LogDebug($"Failed to compile expression '{value}': {e.Message}"); LogDebug($"=== Expression compilation failed ===", _debug);
LogDebug($"Stack trace: {e.StackTrace}"); LogDebug($"Error: {e.Message}", _debug);
// Don't update the config value if invalid LogDebug($"Stack trace: {e.StackTrace}", _debug);
LogDebug($"Expression is invalid but keeping input: '{value}'", _debug);
} }
} }
} }
public bool IsValid => _isValid; public bool IsValid => _isValid;
public string LastValidExpression => _lastValidExpression; public string LastValidExpression => _lastValidExpression;
public string CurrentInput => _currentInput;
public ExpressionConfigEntry(ConfigFile config, string section, string key, string defaultValue, public ExpressionConfigEntry(ConfigFile config, string section, string key, string defaultValue,
string description, ConfigEntry<bool> debug = null) { string description, ConfigEntry<bool> debug = null) {
_debug = debug; _debug = debug;
LogDebug($"Initializing ExpressionConfigEntry for {section}.{key}"); LogDebug($"=== Initializing ExpressionConfigEntry ===", _debug);
LogDebug($"Section: {section}", _debug);
LogDebug($"Key: {key}", _debug);
LogDebug($"Default value: {defaultValue}", _debug);
_configEntry = config.Bind( _configEntry = config.Bind(
section, key, defaultValue, section, key, defaultValue,
new ConfigDescription(description + new ConfigDescription(description +
"\nUse 'v' to represent the input value. Examples: 'v*4', 'v+13', '(v+5)*2'")); "\nUse 'v' to represent the input value. Examples: 'v*4', 'v+13', '(v+5)*2'"));
_currentInput = defaultValue;
// Initial compilation // Initial compilation
try { try {
LogDebug($"Attempting initial compilation of expression: '{_configEntry.Value}'"); LogDebug($"=== Starting initial compilation ===", _debug);
_compiledExpression = ParseExpression(_configEntry.Value); LogDebug($"Initial expression: '{_configEntry.Value}'", _debug);
_compiledExpression = ParseExpression(_configEntry.Value, _debug);
_lastValidExpression = _configEntry.Value; _lastValidExpression = _configEntry.Value;
LogDebug($"Initial expression '{_configEntry.Value}' compiled successfully"); _isValid = true;
LogDebug($"=== Initial compilation successful ===", _debug);
} catch (Exception e) { } catch (Exception e) {
LogDebug($"Failed to compile initial expression '{_configEntry.Value}': {e.Message}"); LogDebug($"=== Initial compilation failed ===", _debug);
LogDebug($"Stack trace: {e.StackTrace}"); LogDebug($"Error: {e.Message}", _debug);
LogDebug($"Stack trace: {e.StackTrace}", _debug);
_isValid = false;
} }
// Recompile when config changes // Recompile when config changes
_configEntry.SettingChanged += (sender, args) => { _configEntry.SettingChanged += (sender, args) => {
try { try {
LogDebug($"Config changed, attempting to recompile expression: '{_configEntry.Value}'"); LogDebug($"=== Config changed, starting recompilation ===", _debug);
_compiledExpression = ParseExpression(_configEntry.Value); LogDebug($"New expression: '{_configEntry.Value}'", _debug);
_compiledExpression = ParseExpression(_configEntry.Value, _debug);
_lastValidExpression = _configEntry.Value; _lastValidExpression = _configEntry.Value;
_isValid = true; _isValid = true;
LogDebug($"Expression '{_configEntry.Value}' recompiled successfully"); _currentInput = _configEntry.Value;
LogDebug($"=== Recompilation successful ===", _debug);
} catch (Exception e) { } catch (Exception e) {
_isValid = false; _isValid = false;
LogDebug($"Failed to recompile expression '{_configEntry.Value}': {e.Message}"); LogDebug($"=== Recompilation failed ===", _debug);
LogDebug($"Stack trace: {e.StackTrace}"); LogDebug($"Error: {e.Message}", _debug);
LogDebug($"Stack trace: {e.StackTrace}", _debug);
LogDebug($"Expression is invalid but keeping input: '{_configEntry.Value}'", _debug);
_currentInput = _configEntry.Value;
} }
}; };
} }
public float Evaluate(float input) { public float Evaluate(float input) {
if (!_isValid) { if (!_isValid) {
LogDebug( LogDebug($"=== Evaluation with invalid expression ===", _debug);
$"Expression is invalid, using last valid expression '{_lastValidExpression}' for input {input}"); LogDebug($"Input value: {input}", _debug);
LogDebug($"Using last valid expression: '{_lastValidExpression}'", _debug);
return input; // Return original value if expression is invalid return input; // Return original value if expression is invalid
} }
LogDebug($"Evaluating expression '{_configEntry.Value}' with input {input}"); LogDebug($"=== Starting expression evaluation ===", _debug);
LogDebug($"Expression: '{_configEntry.Value}'", _debug);
LogDebug($"Input value: {input}", _debug);
var result = _compiledExpression(input); var result = _compiledExpression(input);
LogDebug($"Expression '{_configEntry.Value}' evaluated: {input} -> {result}"); LogDebug($"Output value: {result}", _debug);
LogDebug($"=== Evaluation complete ===", _debug);
return result; return result;
} }
private void LogDebug(string message) { private static void LogDebug(string message, ConfigEntry<bool> debug) {
if (_debug?.Value == true) { if (debug?.Value == true) {
Console.WriteLine($"[ExpressionConfigEntry] {message}"); Console.WriteLine($"[ExpressionConfigEntry] {message}");
} }
} }
// Copy of the expression parser from Main // Copy of the expression parser from Main
private static Func<float, float> ParseExpression(string expr) { private static Func<float, float> ParseExpression(string expr, ConfigEntry<bool> debug) {
try { try {
LogDebug($"=== Starting expression parsing ===", debug);
LogDebug($"Raw expression: '{expr}'", debug);
// Remove all whitespace // Remove all whitespace
expr = expr.Replace(" ", ""); expr = expr.Replace(" ", "");
LogDebug($"After whitespace removal: '{expr}'", debug);
var tokens = new List<string>(); var tokens = new List<string>();
var current = ""; var current = "";
// Tokenize the expression // Tokenize the expression
LogDebug("=== Tokenizing expression ===", debug);
for (int i = 0; i < expr.Length; i++) { for (int i = 0; i < expr.Length; i++) {
char c = expr[i]; char c = expr[i];
if (c == 'v') { if (c == 'v') {
if (current != "") { if (current != "") {
LogDebug($"Found number token: '{current}'", debug);
tokens.Add(current); tokens.Add(current);
current = ""; current = "";
} }
LogDebug("Found variable token: 'v'", debug);
tokens.Add("v"); tokens.Add("v");
} else if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')') { } else if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == ')') {
if (current != "") { if (current != "") {
LogDebug($"Found number token: '{current}'", debug);
tokens.Add(current); tokens.Add(current);
current = ""; current = "";
} }
LogDebug($"Found operator token: '{c}'", debug);
tokens.Add(c.ToString()); tokens.Add(c.ToString());
} else { } else {
current += c; current += c;
} }
} }
if (current != "") { if (current != "") {
LogDebug($"Found final number token: '{current}'", debug);
tokens.Add(current); tokens.Add(current);
} }
LogDebug($"All tokens: {string.Join(", ", tokens)}", debug);
// Convert to postfix notation using shunting yard // Convert to postfix notation using shunting yard
LogDebug("=== Converting to postfix notation ===", debug);
var output = new List<string>(); var output = new List<string>();
var operators = new Stack<string>(); var operators = new Stack<string>();
foreach (var token in tokens) { foreach (var token in tokens) {
if (token == "v" || float.TryParse(token, out _)) { if (token == "v" || float.TryParse(token, out _)) {
LogDebug($"Pushing operand to output: '{token}'", debug);
output.Add(token); output.Add(token);
} else if (token == "(") { } else if (token == "(") {
LogDebug("Pushing '(' to operator stack", debug);
operators.Push(token); operators.Push(token);
} else if (token == ")") { } else if (token == ")") {
LogDebug("Found ')', popping operators until '('", debug);
while (operators.Count > 0 && operators.Peek() != "(") { while (operators.Count > 0 && operators.Peek() != "(") {
output.Add(operators.Pop()); var op = operators.Pop();
LogDebug($"Popped operator to output: '{op}'", debug);
output.Add(op);
} }
if (operators.Count > 0) if (operators.Count > 0) {
LogDebug("Popping '(' from stack", debug);
operators.Pop(); // Remove "(" operators.Pop(); // Remove "("
}
} else { } else {
LogDebug($"Processing operator: '{token}'", debug);
while (operators.Count > 0 && operators.Peek() != "(" && while (operators.Count > 0 && operators.Peek() != "(" &&
GetPrecedence(operators.Peek()) >= GetPrecedence(token)) { GetPrecedence(operators.Peek()) >= GetPrecedence(token)) {
output.Add(operators.Pop()); var op = operators.Pop();
LogDebug($"Popped higher precedence operator to output: '{op}'", debug);
output.Add(op);
} }
LogDebug($"Pushing operator to stack: '{token}'", debug);
operators.Push(token); operators.Push(token);
} }
} }
while (operators.Count > 0) { while (operators.Count > 0) {
output.Add(operators.Pop()); var op = operators.Pop();
LogDebug($"Popping remaining operator to output: '{op}'", debug);
output.Add(op);
} }
LogDebug($"Postfix expression: {string.Join(" ", output)}", debug);
// Build the expression tree // Build the expression tree
LogDebug("=== Building expression tree ===", debug);
var stack = new Stack<Func<float, float>>(); var stack = new Stack<Func<float, float>>();
foreach (var token in output) { foreach (var token in output) {
if (token == "v") { if (token == "v") {
LogDebug("Pushing variable function to stack", debug);
stack.Push(v => v); stack.Push(v => v);
} else if (float.TryParse(token, out float num)) { } else if (float.TryParse(token, out float num)) {
LogDebug($"Pushing constant function to stack: {num}", debug);
stack.Push(v => num); stack.Push(v => num);
} else { } else {
var right = stack.Pop(); var right = stack.Pop();
var left = stack.Pop(); var left = stack.Pop();
LogDebug($"Popped two functions for operator: '{token}'", debug);
switch (token) { switch (token) {
case "+": case "+":
LogDebug("Creating addition function", debug);
stack.Push(v => left(v) + right(v)); stack.Push(v => left(v) + right(v));
break; break;
case "-": case "-":
LogDebug("Creating subtraction function", debug);
stack.Push(v => left(v) - right(v)); stack.Push(v => left(v) - right(v));
break; break;
case "*": case "*":
LogDebug("Creating multiplication function", debug);
stack.Push(v => left(v) * right(v)); stack.Push(v => left(v) * right(v));
break; break;
case "/": case "/":
LogDebug("Creating division function", debug);
stack.Push(v => { stack.Push(v => {
var r = right(v); var r = right(v);
return r != 0 ? left(v) / r : left(v); return r != 0 ? left(v) / r : left(v);
@@ -184,8 +242,11 @@ namespace BanquetForCyka {
} }
} }
LogDebug("=== Expression parsing complete ===", debug);
return stack.Pop(); return stack.Pop();
} catch (Exception e) { } catch (Exception e) {
LogDebug($"=== Expression parsing failed ===", debug);
LogDebug($"Error: {e.Message}", debug);
throw new FormatException($"Failed to parse expression '{expr}': {e.Message}"); throw new FormatException($"Failed to parse expression '{expr}': {e.Message}");
} }
} }