Skip to content

Commit 895b5cf

Browse files
committed
wip: caching checkpoint
1 parent a32c18f commit 895b5cf

5 files changed

Lines changed: 72 additions & 62 deletions

File tree

NTDLS.ExpressionParser/Expression.cs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,13 @@ public Expression(string text, ExpressionOptions? options = null)
6868
State.Reset(Sanitized);
6969
State.ApplyParameters(Sanitized, _definedParameters);
7070

71-
bool isCacheable = ExpressionFunctions.Count == 0;
72-
7371
bool isComplete;
7472
do
7573
{
7674
//Get a sub-expression from the whole expression.
7775
isComplete = AcquireSubexpression(out int startIndex, out int endIndex, out var subExpression);
7876
//Compute the sub-expression.
79-
var resultString = subExpression.Compute(isCacheable);
77+
var resultString = subExpression.Compute();
8078
//Replace the sub-expression in the whole expression with the result from the sub-expression computation.
8179
State.WorkingText = ReplaceRange(State.WorkingText, startIndex, endIndex, resultString);
8280
} while (!isComplete);
@@ -88,7 +86,7 @@ public Expression(string text, ExpressionOptions? options = null)
8886
}
8987

9088
State.HydrateTemplateCache(_expressionHash);
91-
return StringToDouble(State.WorkingText);
89+
return StringToDouble(State.WorkingText, out _);
9290
}
9391

9492
/// <summary>
@@ -98,14 +96,13 @@ public Expression(string text, ExpressionOptions? options = null)
9896
/// <returns></returns>
9997
public double? Evaluate(out string showWork)
10098
{
99+
State.Reset(Sanitized);
101100
State.ApplyParameters(Sanitized, _definedParameters);
102101

103102
var work = new StringBuilder();
104103

105104
work.AppendLine("{");
106105

107-
bool isCacheable = ExpressionFunctions.Count == 0;
108-
109106
bool isComplete;
110107
do
111108
{
@@ -116,7 +113,7 @@ public Expression(string text, ExpressionOptions? options = null)
116113
work.Append(" " + friendlySubExpression);
117114

118115
//Compute the sub-expression.
119-
var resultString = subExpression.Compute(isCacheable);
116+
var resultString = subExpression.Compute();
120117

121118
work.AppendLine($" = {SwapInCacheValues(resultString)}");
122119

@@ -136,7 +133,7 @@ public Expression(string text, ExpressionOptions? options = null)
136133
showWork = work.ToString();
137134

138135
State.HydrateTemplateCache(_expressionHash);
139-
return StringToDouble(State.WorkingText);
136+
return StringToDouble(State.WorkingText, out _);
140137
}
141138

142139
/// <summary>
@@ -353,17 +350,22 @@ internal bool AcquireSubexpression(out int outStartIndex, out int outEndIndex, o
353350
/// <summary>
354351
/// Converts a string or value of a stored cache key into a double.
355352
/// </summary>
356-
internal double? StringToDouble(ReadOnlySpan<char> span)
353+
internal double? StringToDouble(ReadOnlySpan<char> span, out bool isUserVariableDerived)
357354
{
358355
if (span.Length == 0)
359356
{
357+
isUserVariableDerived = false;
360358
return null;
361359
}
362360
else if (span[0] == '$')
363361
{
364-
return State.GetPlaceholderCacheItem(span[1..^1]).ComputedValue;
362+
var placeholder = State.GetPlaceholderCacheItem(span[1..^1]);
363+
isUserVariableDerived = placeholder.IsUserVariableDerived;
364+
return placeholder.ComputedValue;
365365
}
366366

367+
isUserVariableDerived = false;
368+
367369
if (Options.UseFastFloatingPointParser)
368370
{
369371
double result = 0.0;

NTDLS.ExpressionParser/ExpressionState.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public ExpressionState(Sanitized sanitized, ExpressionOptions options)
4545
_placeholderCache[i] = new PlaceholderCacheItem()
4646
{
4747
ComputedValue = options.DefaultNullValue,
48-
IsVariable = false,
48+
IsUserVariableDerived = false,
4949
IsNullValue = true
5050
};
5151
}
@@ -147,7 +147,7 @@ public int ConsumeNextPlaceholderCacheSlot(out string cacheKey)
147147
}
148148

149149
[MethodImpl(MethodImplOptions.AggressiveInlining)]
150-
public string StorePlaceholderCacheItem(double? value, bool isVariable = false)
150+
public string StorePlaceholderCacheItem(double? value, bool isUserVariableDerived = false)
151151
{
152152
var cacheSlot = ConsumeNextPlaceholderCacheSlot(out var cacheKey);
153153

@@ -158,7 +158,7 @@ public string StorePlaceholderCacheItem(double? value, bool isVariable = false)
158158

159159
_placeholderCache[cacheSlot] = new PlaceholderCacheItem()
160160
{
161-
IsVariable = isVariable,
161+
IsUserVariableDerived = isUserVariableDerived,
162162
ComputedValue = value
163163
};
164164

@@ -265,7 +265,7 @@ public void ApplyParameters(Sanitized sanitized, Dictionary<string, double?> def
265265
_placeholderCache[cacheSlot] = new PlaceholderCacheItem()
266266
{
267267
ComputedValue = value ?? _options.DefaultNullValue,
268-
IsVariable = true
268+
IsUserVariableDerived = true
269269
};
270270
WorkingText = WorkingText.Replace(variable, cacheKey);
271271
}

NTDLS.ExpressionParser/SubExpression.cs

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ private bool ProcessFunctionCall()
6464
if (Text[i] == ',')
6565
{
6666
var subExpression = new SubExpression(_parentExpression, _buffer.ToString());
67-
subExpression.Compute(true);
67+
subExpression.Compute();
6868

69-
var param = _parentExpression.StringToDouble(subExpression.Text);
69+
var param = _parentExpression.StringToDouble(subExpression.Text, out _);
7070
foundNull = foundNull || param == null;
7171
if (param != null)
7272
{
@@ -88,9 +88,9 @@ private bool ProcessFunctionCall()
8888
}
8989

9090
var subExpression = new SubExpression(_parentExpression, _buffer.ToString());
91-
subExpression.Compute(true);
91+
subExpression.Compute();
9292

93-
var param = _parentExpression.StringToDouble(subExpression.Text);
93+
var param = _parentExpression.StringToDouble(subExpression.Text, out _);
9494
foundNull = foundNull || param == null;
9595
parameters.Add(param ?? 0);
9696
break;
@@ -105,20 +105,20 @@ private bool ProcessFunctionCall()
105105

106106
if (foundNull)
107107
{
108-
StorePlaceholder(functionStartIndex, functionEndIndex, null);
108+
StorePlaceholder(functionStartIndex, functionEndIndex, null, true);
109109
return true;
110110
}
111111
else if (Utility.IsNativeFunction(foundFunction))
112112
{
113113
double functionResult = Utility.ComputeNativeFunction(foundFunction, parameters.ToArray());
114-
StorePlaceholder(functionStartIndex, functionEndIndex, functionResult);
114+
StorePlaceholder(functionStartIndex, functionEndIndex, functionResult, true);
115115
}
116116
else
117117
{
118118
if (_parentExpression.ExpressionFunctions.TryGetValue(foundFunction, out var customFunction))
119119
{
120120
var functionResult = customFunction.Invoke(parameters.ToArray()) ?? _parentExpression.Options.DefaultNullValue;
121-
StorePlaceholder(functionStartIndex, functionEndIndex, functionResult);
121+
StorePlaceholder(functionStartIndex, functionEndIndex, functionResult, true);
122122
}
123123
else
124124
{
@@ -132,57 +132,54 @@ private bool ProcessFunctionCall()
132132
return false;
133133
}
134134

135-
internal string Compute(bool isCacheable)
135+
internal string Compute()
136136
{
137137
TruncateParenthesizes();
138138

139139
//Process all function calls from right-to-left.
140-
while (true)
140+
while (ProcessFunctionCall())
141141
{
142-
if (ProcessFunctionCall())
143-
{
144-
isCacheable = false;
145-
}
146-
else
147-
{
148-
break;
149-
}
150142
}
151143

144+
bool isAnyUserVariableDerived = false;
145+
152146
while (true)
153147
{
154148
int operatorIndex;
155149

156150
//Pre-first-order:
157151
while ((operatorIndex = GetFreestandingNotOperation(out _)) != -1)
158152
{
159-
var rightValue = GetRightValue(operatorIndex + 1, out int outParsedLength, out bool isOperationCacheable);
153+
var rightValue = GetRightValue(operatorIndex + 1, out int outParsedLength, out bool isUserVariableDerived);
154+
isAnyUserVariableDerived = isAnyUserVariableDerived || isUserVariableDerived;
160155
int? calculatedResult = rightValue == null ? null : (rightValue == 0) ? 1 : 0;
161-
162-
StorePlaceholder(operatorIndex, operatorIndex + outParsedLength, calculatedResult);
156+
StorePlaceholder(operatorIndex, operatorIndex + outParsedLength, calculatedResult, isUserVariableDerived);
163157
}
164158

165159
//First order operations:
166160
operatorIndex = GetIndexOfOperation(Utility.FirstOrderOperations, out string operation);
167161
if (operatorIndex > 0)
168162
{
169-
CollapseRightAndLeft(operation, operatorIndex);
163+
CollapseRightAndLeft(operation, operatorIndex, out bool isUserVariableDerived);
164+
isAnyUserVariableDerived = isAnyUserVariableDerived || isUserVariableDerived;
170165
continue;
171166
}
172167

173168
//Second order operations:
174169
operatorIndex = GetIndexOfOperation(Utility.SecondOrderOperations, out operation);
175170
if (operatorIndex > 0)
176171
{
177-
CollapseRightAndLeft(operation, operatorIndex);
172+
CollapseRightAndLeft(operation, operatorIndex, out bool isUserVariableDerived);
173+
isAnyUserVariableDerived = isAnyUserVariableDerived || isUserVariableDerived;
178174
continue;
179175
}
180176

181177
//Third order operations:
182178
operatorIndex = GetIndexOfOperation(Utility.ThirdOrderOperations, out operation);
183179
if (operatorIndex > 0)
184180
{
185-
CollapseRightAndLeft(operation, operatorIndex);
181+
CollapseRightAndLeft(operation, operatorIndex, out bool isUserVariableDerived);
182+
isAnyUserVariableDerived = isAnyUserVariableDerived || isUserVariableDerived;
186183
continue;
187184
}
188185

@@ -195,12 +192,12 @@ internal string Compute(bool isCacheable)
195192
return Text;
196193
}
197194

198-
return _parentExpression.State.StorePlaceholderCacheItem(_parentExpression.StringToDouble(Text));
195+
return _parentExpression.State.StorePlaceholderCacheItem(_parentExpression.StringToDouble(Text, out _));
199196
}
200197

201-
internal void StorePlaceholder(int startIndex, int endIndex, double? value)
198+
internal void StorePlaceholder(int startIndex, int endIndex, double? value, bool isUserVariableDerived)
202199
{
203-
var cacheKey = _parentExpression.State.StorePlaceholderCacheItem(value);
200+
var cacheKey = _parentExpression.State.StorePlaceholderCacheItem(value, isUserVariableDerived);
204201
Text = _parentExpression.ReplaceRange(Text, startIndex, endIndex, cacheKey);
205202
}
206203

@@ -219,17 +216,19 @@ internal void TruncateParenthesizes()
219216
/// Gets the numbers to the left and right of an operator.
220217
/// Returns FALSE when NULL is found for either value.
221218
/// </summary>
222-
private void CollapseRightAndLeft(string operation, int operationBeginIndex)
219+
private void CollapseRightAndLeft(string operation, int operationBeginIndex, out bool isUserVariableDerived)
223220
{
224-
var left = GetLeftValue(operationBeginIndex, out int leftParsedLength, out bool isLeftCacheable);
225-
var right = GetRightValue(operationBeginIndex + operation.Length, out int rightParsedLength, out bool isRightCacheable);
221+
var left = GetLeftValue(operationBeginIndex, out int leftParsedLength, out bool isLeftUserVariableDerived);
222+
var right = GetRightValue(operationBeginIndex + operation.Length, out int rightParsedLength, out bool isRightUserVariableDerived);
226223

227224
var beginPosition = operationBeginIndex - leftParsedLength;
228225
var endPosition = operationBeginIndex + rightParsedLength + (operation.Length - 1);
229226

227+
isUserVariableDerived = isLeftUserVariableDerived || isRightUserVariableDerived;
228+
230229
if (_parentExpression.State.TryGetComputedStep(out ComputedStepItem cachedObj))
231230
{
232-
StorePlaceholder(cachedObj.BeginPosition, cachedObj.EndPosition, cachedObj.ParsedValue);
231+
StorePlaceholder(cachedObj.BeginPosition, cachedObj.EndPosition, cachedObj.ParsedValue, isUserVariableDerived);
233232
}
234233
else
235234
{
@@ -240,8 +239,8 @@ private void CollapseRightAndLeft(string operation, int operationBeginIndex)
240239
result = Utility.ComputePrivative(left ?? 0, operation, right ?? 0);
241240
}
242241

243-
StorePlaceholder(beginPosition, endPosition, result);
244-
if (isLeftCacheable && isRightCacheable)
242+
StorePlaceholder(beginPosition, endPosition, result, isUserVariableDerived);
243+
if (!isLeftUserVariableDerived && !isRightUserVariableDerived)
245244
{
246245
var parsedNumber = new ComputedStepItem
247246
{
@@ -258,12 +257,12 @@ private void CollapseRightAndLeft(string operation, int operationBeginIndex)
258257
}
259258
}
260259

261-
private double? GetLeftValue(int operationIndex, out int outParsedLength, out bool isCacheable)
260+
private double? GetLeftValue(int operationIndex, out int outParsedLength, out bool isUserVariableDerived)
262261
{
263262
if (_parentExpression.State.TryGetScanStep(out var cachedObj))
264263
{
265264
outParsedLength = cachedObj.Length;
266-
isCacheable = true;
265+
isUserVariableDerived = false;
267266
return cachedObj.Value;
268267
}
269268
else
@@ -283,7 +282,7 @@ private void CollapseRightAndLeft(string operation, int operationBeginIndex)
283282
outParsedLength = (operationIndex - i) - 1;
284283
var cacheKey = span.Slice(operationIndex - outParsedLength + 1, outParsedLength - 2);
285284
var cachedItem = _parentExpression.State.GetPlaceholderCacheItem(cacheKey);
286-
isCacheable = !cachedItem.IsVariable;
285+
isUserVariableDerived = cachedItem.IsUserVariableDerived;
287286
_parentExpression.State.IncrementScanStep();
288287
return cachedItem.ComputedValue;
289288
}
@@ -307,8 +306,7 @@ private void CollapseRightAndLeft(string operation, int operationBeginIndex)
307306
}
308307

309308
outParsedLength = (operationIndex - i) - 1;
310-
isCacheable = true;
311-
var result = _parentExpression.StringToDouble(span.Slice(operationIndex - outParsedLength, outParsedLength));
309+
var result = _parentExpression.StringToDouble(span.Slice(operationIndex - outParsedLength, outParsedLength), out isUserVariableDerived);
312310

313311
_parentExpression.State.StoreScanStep(new ScanStepItem
314312
{
@@ -321,12 +319,12 @@ private void CollapseRightAndLeft(string operation, int operationBeginIndex)
321319
}
322320
}
323321

324-
private double? GetRightValue(int endOfOperationIndex, out int outParsedLength, out bool isCacheable)
322+
private double? GetRightValue(int endOfOperationIndex, out int outParsedLength, out bool isUserVariableDerived)
325323
{
326324
if (_parentExpression.State.TryGetScanStep(out var cachedObj))
327325
{
328326
outParsedLength = cachedObj.Length;
329-
isCacheable = true;
327+
isUserVariableDerived = false;
330328
return cachedObj.Value;
331329
}
332330
else
@@ -345,7 +343,7 @@ private void CollapseRightAndLeft(string operation, int operationBeginIndex)
345343
i++;
346344
outParsedLength = i;
347345
var cachedItem = _parentExpression.State.GetPlaceholderCacheItem(span.Slice(1, i - 2));
348-
isCacheable = !cachedItem.IsVariable;
346+
isUserVariableDerived = cachedItem.IsUserVariableDerived;
349347
_parentExpression.State.IncrementScanStep();
350348
return cachedItem.ComputedValue;
351349
}
@@ -362,8 +360,7 @@ private void CollapseRightAndLeft(string operation, int operationBeginIndex)
362360
}
363361

364362
outParsedLength = i;
365-
isCacheable = true;
366-
var result = _parentExpression.StringToDouble(span.Slice(0, i));
363+
var result = _parentExpression.StringToDouble(span.Slice(0, i), out isUserVariableDerived);
367364

368365
_parentExpression.State.StoreScanStep(new ScanStepItem
369366
{

NTDLS.ExpressionParser/Types.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ internal struct ScanStepItem
2323
internal struct PlaceholderCacheItem
2424
{
2525
public double? ComputedValue;
26-
public bool IsVariable;
26+
public bool IsUserVariableDerived;
2727
public bool IsNullValue;
2828
}
2929
}

TestHarness/Program.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,22 @@ static void Baseline()
2929

3030
static void Main()
3131
{
32-
var expression = new Expression("10 * ((5 + 1000 + ( 10 )) * 60.5) * 10");
33-
Console.WriteLine("---FIRST---");
34-
Console.WriteLine(expression.Evaluate()); //20
35-
Console.WriteLine("---SECOND---");
36-
Console.WriteLine(expression.Evaluate()); //20
32+
var expression = new Expression("10 * ((5 + extra + CustomSum(11,55) + ( 10 + !0 )) * Ceil(SUM(11.6, 12.5, 14.7, 11.11)) + 60.5) * 10");
33+
34+
expression.SetParameter("extra", 1000);
35+
36+
expression.AddFunction("CustomSum", parameters =>
37+
{
38+
return null;
39+
});
40+
41+
Console.WriteLine(expression.Evaluate()?.ToString() ?? "{NULL}");
42+
43+
//var expression = new Expression("10 * ((5 + 1000 + ( 10 )) * 60.5) * 10");
44+
//Console.WriteLine("---FIRST---");
45+
//Console.WriteLine(expression.Evaluate()); //20
46+
//Console.WriteLine("---SECOND---");
47+
//Console.WriteLine(expression.Evaluate()); //20
3748
//Console.WriteLine(expression.Evaluate()); //20
3849

3950
//expression.SetParameter("extra", 10);

0 commit comments

Comments
 (0)