using System; using System.Collections.Generic; using BepInEx.Configuration; using UnityEngine; namespace BanquetForCyka { public class ExpressionConfigEntry { private readonly ConfigEntry _configEntry; private readonly ConfigEntry _debug; private Func _compiledExpression; private string _lastValidExpression; private bool _isValid = true; private string _currentInput; public string Value { get => _configEntry.Value; set { if (value == _configEntry.Value) return; _currentInput = value; // Try to compile the expression try { LogDebug($"=== Starting compilation of new expression ===", _debug); LogDebug($"Input expression: '{value}'", _debug); var newExpr = ParseExpression(value, _debug); _compiledExpression = newExpr; _lastValidExpression = value; _isValid = true; LogDebug($"=== Expression compilation successful ===", _debug); } catch (Exception e) { _isValid = false; 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 ===", _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($"=== Starting initial compilation ===", _debug); LogDebug($"Initial expression: '{_configEntry.Value}'", _debug); _compiledExpression = ParseExpression(_configEntry.Value, _debug); _lastValidExpression = _configEntry.Value; _isValid = true; LogDebug($"=== Initial compilation successful ===", _debug); } catch (Exception e) { 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, starting recompilation ===", _debug); LogDebug($"New expression: '{_configEntry.Value}'", _debug); _compiledExpression = ParseExpression(_configEntry.Value, _debug); _lastValidExpression = _configEntry.Value; _isValid = true; _currentInput = _configEntry.Value; LogDebug($"=== Recompilation successful ===", _debug); } catch (Exception e) { _isValid = false; 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($"=== 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($"=== Starting expression evaluation ===", _debug); LogDebug($"Expression: '{_configEntry.Value}'", _debug); LogDebug($"Input value: {input}", _debug); var result = _compiledExpression(input); LogDebug($"Output value: {result}", _debug); LogDebug($"=== Evaluation complete ===", _debug); return result; } 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, 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() != "(") { var op = operators.Pop(); LogDebug($"Popped operator to output: '{op}'", debug); output.Add(op); } 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)) { 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) { 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); }); break; } } } 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}"); } } private static int GetPrecedence(string op) { switch (op) { case "+": return 1; case "-": return 1; case "*": return 2; case "/": return 2; default: return 0; } } } }