namespace NTDLS.ExpressionParser
{
internal class SubExpression
{
private readonly Expression _parentExpression;
public string Text { get; internal set; }
public SubExpression(Expression parentExpression, string text)
{
_parentExpression = parentExpression;
Text = text;
}
private int GetStartingIndexOfLastFunctionCall(out string foundFunction)
{
int foundIndex = -1;
foundFunction = string.Empty;
foreach (var function in _parentExpression.DiscoveredFunctions)
{
int index = Text.LastIndexOf(function);
if (index >= 0 && index > foundIndex)
{
foundIndex = index;
foundFunction = function;
}
}
return foundIndex;
}
private bool ProcessFunctionCall()
{
string foundFunction;
int functionStartIndex = GetStartingIndexOfLastFunctionCall(out foundFunction);
int functionEndIndex;
if (functionStartIndex >= 0)
{
string buffer = string.Empty;
int scope = 0;
List parameters = new();
int i = functionStartIndex + foundFunction.Length; //Skip the function name.
for (; i < Text.Length; i++)
{
if (Text[i] == ',')
{
var subExpression = new SubExpression(_parentExpression, buffer);
subExpression.Compute();
parameters.Add(double.Parse(subExpression.Text));
buffer = string.Empty;
}
else if (Text[i] == '{')
{
scope++;
}
else if (Text[i] == '}')
{
scope--;
if (scope != 0)
{
throw new Exception("Unexpected function nesting.");
}
var subExpression = new SubExpression(_parentExpression, buffer);
subExpression.Compute();
var param = _parentExpression.ExpToDouble(subExpression.Text);
parameters.Add(param);
break;
}
else
{
buffer += Text[i];
}
}
functionEndIndex = i;
if (foundFunction.IsNativeFunction())
{
double functionResult = foundFunction.ComputeNativeFunction(parameters.ToArray());
ReplaceRange(functionStartIndex, functionEndIndex, functionResult);
}
else
{
if (_parentExpression.CustomFunctions.TryGetValue(foundFunction, out var customFunction))
{
double functionResult = customFunction.Invoke(parameters.ToArray());
ReplaceRange(functionStartIndex, functionEndIndex, functionResult);
}
else
{
throw new Exception($"Undefined function: {foundFunction}");
}
}
return true;
}
return false;
}
internal string Compute()
{
TruncateParenthesizes();
while (true)
{
int operatorIndex;
//Process all function calls from right-to-left.
while (ProcessFunctionCall()) { }
//Pre-first-order:
while ((operatorIndex = GetFreestandingNotOperation(out _)) > 0)
{
double rightValue = GetRightValue(operatorIndex + 1, out int outParsedLength);
int notResult = (rightValue == 0) ? 1 : 0;
ReplaceRange(operatorIndex, operatorIndex + outParsedLength, notResult);
}
//First order operations:
operatorIndex = GetIndexOfOperation(Constants.FirstOrderOperations, out string operation);
if (operatorIndex > 0)
{
GetLeftAndRightValues(operation, operatorIndex, out double leftValue, out double rightValue, out int beginPosition, out int endPosition);
double calculatedResult = operation.ComputePrivative(leftValue, rightValue);
ReplaceRange(beginPosition, endPosition, calculatedResult);
continue;
}
//Second order operations:
operatorIndex = GetIndexOfOperation(Constants.SecondOrderOperations, out operation);
if (operatorIndex > 0)
{
GetLeftAndRightValues(operation, operatorIndex, out double leftValue, out double rightValue, out int beginPosition, out int endPosition);
double calculatedResult = operation.ComputePrivative(leftValue, rightValue);
ReplaceRange(beginPosition, endPosition, calculatedResult);
continue;
}
//Third order operations:
operatorIndex = GetIndexOfOperation(Constants.ThirdOrderOperations, out operation);
if (operatorIndex > 0)
{
GetLeftAndRightValues(operation, operatorIndex, out double leftValue, out double rightValue, out int beginPosition, out int endPosition);
double calculatedResult = operation.ComputePrivative(leftValue, rightValue);
ReplaceRange(beginPosition, endPosition, calculatedResult);
continue;
}
break;
}
if (Text[0] == '$')
{
//We already have this value in the cache.
return Text;
}
var cacheKey = _parentExpression.ConsumeNextComputedCacheIndex();
_parentExpression.ComputedCache[cacheKey.Index] = double.Parse(Text);
return cacheKey.Placeholder;
}
internal void ReplaceRange(int startIndex, int endIndex, double value)
{
var cacheKey = _parentExpression.ConsumeNextComputedCacheIndex();
_parentExpression.ComputedCache[cacheKey.Index] = value;
Text = _parentExpression.ReplaceRange(Text, startIndex, endIndex, cacheKey.Placeholder);
}
///
/// Removes leading and trailing parenthesizes, if they exist.
///
internal void TruncateParenthesizes()
{
while (Text.StartsWith('(') && Text.EndsWith(')'))
{
Text = Text[1..^1];
}
}
///
/// Gets the numbers to the left and right of an operator.
///
private void GetLeftAndRightValues(string operation,
int operationBeginIndex, out double leftValue, out double rightValue, out int beginPosition, out int endPosition)
{
leftValue = GetLeftValue(operationBeginIndex, out int leftParsedLength);
rightValue = GetRightValue(operationBeginIndex + operation.Length, out int rightParsedLength);
beginPosition = operationBeginIndex - leftParsedLength;
endPosition = operationBeginIndex + rightParsedLength + (operation.Length - 1);
}
private double GetLeftValue(int operationIndex, out int outParsedLength)
{
int i = operationIndex - 1;
for (; i > -1; i--)
{
if (((Text[i] - '0') >= 0 && (Text[i] - '0') <= 9) || Text[i] == '.' || Text[i] == '$')
{
}
else if (Text[i] == '-' || Text[i] == '+')
{
if (i == 0)
{
//The first character is a + or -, this is a valid explicit positive or negative.
}
else if (Text[i - 1].IsMathCharacter())
{
//The next character to the left is a match, this is a valid explicit positive or negative.
}
else
{
break;
}
}
else
{
break;
}
}
outParsedLength = (operationIndex - 1) - i;
string value = Text.Substring(i + 1, outParsedLength);
if (value[0] == '$')
{
return _parentExpression.ComputedCache[value.ToCacheIndex()];
}
return double.Parse(value);
}
private double GetRightValue(int endOfOperationIndex, out int outParsedLength)
{
int i = endOfOperationIndex;
for (; i < Text.Length; i++)
{
if (i == endOfOperationIndex && (Text[i] == '-' || Text[i] == '+') || Text[i] == '$')
{
}
else if (((Text[i] - '0') >= 0 && (Text[i] - '0') <= 9) || (Text[i] == '.'))
{
}
else
{
break;
}
}
outParsedLength = i - endOfOperationIndex;
string value = Text.Substring(endOfOperationIndex, outParsedLength);
if (value[0] == '$')
{
return _parentExpression.ComputedCache[value.ToCacheIndex()];
}
return double.Parse(value);
}
private int GetFreestandingNotOperation(out string outFoundOperation) //Pre order.
{
for (int i = 0; i < Text.Length; i++)
{
//Make sure we have a "!' and not a "!=", these two have to be handled in different places.
if (Text[i] == '!' && (i + 1 < Text.Length) && Text[i + 1] != '=')
{
outFoundOperation = Text[i].ToString();
return i;
}
}
outFoundOperation = string.Empty; //No operation found.
return -1;
}
private int GetIndexOfOperation(char[] validOperations, out string outFoundOperation)
{
for (int i = 1; i < Text.Length; i++)
{
if (validOperations.Contains(Text[i]))
{
outFoundOperation = Text[i].ToString();
return i;
}
}
outFoundOperation = string.Empty; //No operation found.
return -1;
}
private int GetIndexOfOperation(string[] validOperations, out string outFoundOperation)
{
int foundIndex = -1;
string foundOperation = string.Empty;
for (int i = 0; i < validOperations.Length; i++)
{
int index = Text.IndexOf(validOperations[i], 1);
if (index >= 1 && (foundIndex < 0 || index < foundIndex))
{
foundIndex = index;
foundOperation = validOperations[i];
}
}
if (foundIndex >= 0)
{
outFoundOperation = foundOperation; //No operation found.
return foundIndex;
}
outFoundOperation = string.Empty; //No operation found.
return -1;
}
}
} Last modified by Admin @ 10/22/2025 3:55:31 PM
Comments
Login to leave a comment.View all comments