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