uCalc API Version: 2.1.3-preview.2 Released: 6/16/2026

Warning

uCalc API Preview Release Notice:The documentation describes the intended behavior of the API. The current preview build contains incomplete features, unoptimized performance, and is subject to breaking changes.

RewindOnChange = [bool]

Property

Product: 

Transformer Library

Class: 

Rule

Controls whether the transformer re-scans text from the beginning of a match after a replacement, enabling recursive or cascading transformations.

Remarks

🔄 Recursive Transformations with RewindOnChange

The RewindOnChange property controls the Transformer's behavior after it successfully applies a rule. It allows you to create powerful recursive or cascading transformations that would otherwise require complex, multi-pass logic.

  • false (Default): After a replacement, the Transformer continues scanning from the position after the newly inserted text. It will not re-evaluate the text that was just modified.

  • true: After a replacement, the Transformer "rewinds" its cursor to the start of the original match location and re-scans the text. This allows other rules—or even the same rule—to find new matches within the just-transformed text.

🎯 Core Use Case: Recursive and Cascading Rules

This property is the key to creating rules that expand recursively. A common use case is implementing variadic functions in a pre-processing step. Consider a pattern to expand Add(1, 2, 3) into (1 + (2 + 3)).

// Without Rewind, only the first match is found.var t = new uCalc.Transformer();t.FromTo("Add({x}, {y})", "({x} + Add({y}))");Console.WriteLine(t.Transform("Add(1,2,3)"));// Output: (1 + Add(2,3)) - The inner Add(2,3) is not expanded.// With Rewind, the expansion continues until no matches are left.t.Reset();t.FromTo("Add({x}, {y})", "({x} + Add({y}))").RewindOnChange = true;Console.WriteLine(t.Transform("Add(1,2,3)"));// Output: (1 + (2 + 3))

This works because after the first transform, the string becomes (1 + Add(2,3)). The rewind causes the parser to re-scan this, find Add(2,3), and apply the rule again.

⚠️ Infinite Loop Warning

Use @RewindOnChange with caution. If your rules can transform text in a cyclical manner, you will create an infinite loop.

Example of an Infinite Loop:

var t = new uCalc.Transformer();t.FromTo("A", "B").SetRewindOnChange(true);t.FromTo("B", "A").SetRewindOnChange(true);// This will cause an infinite loop: A -> B -> A -> B ...// t.Transform("A"); 

To prevent this, ensure your transformation rules are always reducing the input towards a final, non-matching state, or use a Maximum match count to terminate the process.

💡 Why uCalc? (Comparative Analysis)

In a traditional regex-based workflow, achieving a recursive transformation requires manual, imperative code:

// Manual approach without uCalcstring text = "Add(1,2,3)";while (text.Contains("Add(")) {    text = Regex.Replace(text, "Add\\((.*?),(.*)\\)", "($1 + Add($2))");}

This approach is slow, brittle, and mixes transformation logic with control flow. uCalc's @RewindOnChange(true) is declarative. You simply state your intent on the rule, and the engine handles the complex looping and re-scanning logic internally in a highly optimized manner.

Per-Rule vs. Global Setting

You can set this property on an individual Rule for granular control, or you can set it on the Transformer.DefaultRuleSet to have it apply to all subsequently defined rules in that transformer.

Examples

A succinct example demonstrating the cascading effect of enabling `RewindOnChange`.
				
					using uCalcSoftware;

var uc = new uCalc();
var t = new uCalc.Transformer();
// Rule 1: Replace 'A' with 'B'. Rewind is off by default.
t.FromTo("A", "B");
// Rule 2: Replace 'B' with 'C'.
t.FromTo("B", "C");

Console.WriteLine("--- Rewind Disabled ---");
// The 'A' becomes 'B', but the scan continues *after* the 'B', so rule 2 is not triggered.
Console.WriteLine(t.Transform("Start A End"));
t.Reset();

// Now, enable rewind on the first rule.
t.FromTo("A", "B").RewindOnChange = true;
t.FromTo("B", "C");

Console.WriteLine("");
Console.WriteLine("--- Rewind Enabled ---");
// The 'A' becomes 'B', rewind occurs, the 'B' is re-scanned and becomes 'C'.
Console.WriteLine(t.Transform("Start A End"));
				
			
--- Rewind Disabled ---
Start B End

--- Rewind Enabled ---
Start C End
				
					#include <iostream>
#include "uCalc.h"

using namespace std;
using namespace uCalcSoftware;

int main() {
   uCalc uc;
   uCalc::Transformer t;
   // Rule 1: Replace 'A' with 'B'. Rewind is off by default.
   t.FromTo("A", "B");
   // Rule 2: Replace 'B' with 'C'.
   t.FromTo("B", "C");

   cout << "--- Rewind Disabled ---" << endl;
   // The 'A' becomes 'B', but the scan continues *after* the 'B', so rule 2 is not triggered.
   cout << t.Transform("Start A End") << endl;
   t.Reset();

   // Now, enable rewind on the first rule.
   t.FromTo("A", "B").RewindOnChange(true);
   t.FromTo("B", "C");

   cout << "" << endl;
   cout << "--- Rewind Enabled ---" << endl;
   // The 'A' becomes 'B', rewind occurs, the 'B' is re-scanned and becomes 'C'.
   cout << t.Transform("Start A End") << endl;
}
				
			
--- Rewind Disabled ---
Start B End

--- Rewind Enabled ---
Start C End
				
					Imports System
Imports uCalcSoftware
Public Module Program
   Public Sub Main()
      Dim uc As New uCalc()
      Dim t As New uCalc.Transformer()
      '// Rule 1: Replace 'A' with 'B'. Rewind is off by default.
      t.FromTo("A", "B")
      '// Rule 2: Replace 'B' with 'C'.
      t.FromTo("B", "C")
      
      Console.WriteLine("--- Rewind Disabled ---")
      '// The 'A' becomes 'B', but the scan continues *after* the 'B', so rule 2 is not triggered.
      Console.WriteLine(t.Transform("Start A End"))
      t.Reset()
      
      '// Now, enable rewind on the first rule.
      t.FromTo("A", "B").RewindOnChange = true
      t.FromTo("B", "C")
      
      Console.WriteLine("")
      Console.WriteLine("--- Rewind Enabled ---")
      '// The 'A' becomes 'B', rewind occurs, the 'B' is re-scanned and becomes 'C'.
      Console.WriteLine(t.Transform("Start A End"))
   End Sub
End Module
				
			
--- Rewind Disabled ---
Start B End

--- Rewind Enabled ---
Start C End
Demonstrates using `RewindOnChange` to create a recursive `AddUp` function within the expression transformer.
				
					using uCalcSoftware;

var uc = new uCalc();
var t = uc.ExpressionTransformer;  // Transformer used for Eval() and Evaluate()

var p1 = t.FromTo("AddUp({x})", "{x}"); // Base case
var p2 = t.FromTo("AddUp({x}, {y})", "({x} + AddUp({y}))").SetRewindOnChange(true); // Recursive step

Console.WriteLine($"p1 RewindOnChange: {p1.RewindOnChange}");
Console.WriteLine($"p2 RewindOnChange: {p2.RewindOnChange}");

Console.WriteLine("");
Console.WriteLine($"Input: AddUp(1,2,3,4)");
Console.WriteLine($"Transform: {t.Transform("AddUp(1,2,3,4)")}");
Console.WriteLine($"Eval: {uc.Eval("AddUp(1,2,3,4)")}");
				
			
p1 RewindOnChange: False
p2 RewindOnChange: True

Input: AddUp(1,2,3,4)
Transform: (1 + (2 + (3 + 4)))
Eval: 10
				
					#include <iostream>
#include "uCalc.h"

using namespace std;
using namespace uCalcSoftware;

#define tf(IsTrue) ((IsTrue) ? "True" : "False")

int main() {
   uCalc uc;
   auto t = uc.ExpressionTransformer();  // Transformer used for Eval() and Evaluate()

   auto p1 = t.FromTo("AddUp({x})", "{x}"); // Base case
   auto p2 = t.FromTo("AddUp({x}, {y})", "({x} + AddUp({y}))").SetRewindOnChange(true); // Recursive step

   cout << "p1 RewindOnChange: " << tf(p1.RewindOnChange()) << endl;
   cout << "p2 RewindOnChange: " << tf(p2.RewindOnChange()) << endl;

   cout << "" << endl;
   cout << "Input: " << "AddUp(1,2,3,4)" << endl;
   cout << "Transform: " << t.Transform("AddUp(1,2,3,4)") << endl;
   cout << "Eval: " << uc.Eval("AddUp(1,2,3,4)") << endl;
}
				
			
p1 RewindOnChange: False
p2 RewindOnChange: True

Input: AddUp(1,2,3,4)
Transform: (1 + (2 + (3 + 4)))
Eval: 10
				
					Imports System
Imports uCalcSoftware
Public Module Program
   Public Sub Main()
      Dim uc As New uCalc()
      Dim t = uc.ExpressionTransformer  '// Transformer used for Eval() and Evaluate()
      
      Dim p1 = t.FromTo("AddUp({x})", "{x}") '// Base case
      Dim p2 = t.FromTo("AddUp({x}, {y})", "({x} + AddUp({y}))").SetRewindOnChange(true) '// Recursive step
      
      Console.WriteLine($"p1 RewindOnChange: {p1.RewindOnChange}")
      Console.WriteLine($"p2 RewindOnChange: {p2.RewindOnChange}")
      
      Console.WriteLine("")
      Console.WriteLine($"Input: AddUp(1,2,3,4)")
      Console.WriteLine($"Transform: {t.Transform("AddUp(1,2,3,4)")}")
      Console.WriteLine($"Eval: {uc.Eval("AddUp(1,2,3,4)")}")
   End Sub
End Module
				
			
p1 RewindOnChange: False
p2 RewindOnChange: True

Input: AddUp(1,2,3,4)
Transform: (1 + (2 + (3 + 4)))
Eval: 10
Applies `RewindOnChange` to a default rule set to enable complex, multi-rule transformations for a custom `Average` function.
				
					using uCalcSoftware;

var uc = new uCalc();
var t = uc.ExpressionTransformer;

// Enable rewind for all subsequent rules in this transformer.
t.DefaultRuleSet.SetRewindOnChange(true);

// Define the recursive rules.
t.FromTo("AddUp({x})", "{x}");
t.FromTo("AddUp({x}, {y})", "({x} + AddUp({y}))");

t.FromTo("ArgCount({x})", "1");
t.FromTo("ArgCount({x}, {y})", "(1 + ArgCount({y}))");

// The main rule that combines the others.
t.FromTo("Average({x}, {y})", "AddUp({x}, {y}) / ArgCount({x}, {y})");

var expression = "Average(1, 2, 3, 4)";
Console.WriteLine($"Input: {expression}");
Console.WriteLine($"Transform: {t.Transform(expression)}");
Console.WriteLine($"Eval: {uc.Eval(expression)}");
				
			
Input: Average(1, 2, 3, 4)
Transform: (1 + (2 + (3 + 4))) / (1 + (1 + (1 + 1)))
Eval: 2.5
				
					#include <iostream>
#include "uCalc.h"

using namespace std;
using namespace uCalcSoftware;

int main() {
   uCalc uc;
   auto t = uc.ExpressionTransformer();

   // Enable rewind for all subsequent rules in this transformer.
   t.DefaultRuleSet().SetRewindOnChange(true);

   // Define the recursive rules.
   t.FromTo("AddUp({x})", "{x}");
   t.FromTo("AddUp({x}, {y})", "({x} + AddUp({y}))");

   t.FromTo("ArgCount({x})", "1");
   t.FromTo("ArgCount({x}, {y})", "(1 + ArgCount({y}))");

   // The main rule that combines the others.
   t.FromTo("Average({x}, {y})", "AddUp({x}, {y}) / ArgCount({x}, {y})");

   auto expression = "Average(1, 2, 3, 4)";
   cout << "Input: " << expression << endl;
   cout << "Transform: " << t.Transform(expression) << endl;
   cout << "Eval: " << uc.Eval(expression) << endl;
}
				
			
Input: Average(1, 2, 3, 4)
Transform: (1 + (2 + (3 + 4))) / (1 + (1 + (1 + 1)))
Eval: 2.5
				
					Imports System
Imports uCalcSoftware
Public Module Program
   Public Sub Main()
      Dim uc As New uCalc()
      Dim t = uc.ExpressionTransformer
      
      '// Enable rewind for all subsequent rules in this transformer.
      t.DefaultRuleSet.SetRewindOnChange(true)
      
      '// Define the recursive rules.
      t.FromTo("AddUp({x})", "{x}")
      t.FromTo("AddUp({x}, {y})", "({x} + AddUp({y}))")
      
      t.FromTo("ArgCount({x})", "1")
      t.FromTo("ArgCount({x}, {y})", "(1 + ArgCount({y}))")
      
      '// The main rule that combines the others.
      t.FromTo("Average({x}, {y})", "AddUp({x}, {y}) / ArgCount({x}, {y})")
      
      Dim expression = "Average(1, 2, 3, 4)"
      Console.WriteLine($"Input: {expression}")
      Console.WriteLine($"Transform: {t.Transform(expression)}")
      Console.WriteLine($"Eval: {uc.Eval(expression)}")
   End Sub
End Module
				
			
Input: Average(1, 2, 3, 4)
Transform: (1 + (2 + (3 + 4))) / (1 + (1 + (1 + 1)))
Eval: 2.5
Using ExpressionTransformer to transform expressions before they are parsed
				
					using uCalcSoftware;

var uc = new uCalc();
var ExprT = uc.ExpressionTransformer;  // Transformer used for Eval() and Evaluate()

var p1 = ExprT.FromTo("AddUp({x})", "{x}"); // RewindOnChange False by default
var p2 = ExprT.FromTo("AddUp({x}, {y})", "({x} + AddUp({y}))").SetRewindOnChange(true);

Console.WriteLine($"p1 RewindOnChange: {p1.RewindOnChange}");
Console.WriteLine($"p2 RewindOnChange: {p2.RewindOnChange}");

Console.WriteLine("");

Console.WriteLine($"Input: AddUp(1,2,3,4)");
Console.WriteLine($"Transform: {ExprT.Transform("AddUp(1,2,3,4)")}");
Console.WriteLine($"Eval: {uc.Eval("AddUp(1,2,3,4)")}");
				
			
p1 RewindOnChange: False
p2 RewindOnChange: True

Input: AddUp(1,2,3,4)
Transform: (1 + (2 + (3 + 4)))
Eval: 10
				
					#include <iostream>
#include "uCalc.h"

using namespace std;
using namespace uCalcSoftware;

#define tf(IsTrue) ((IsTrue) ? "True" : "False")

int main() {
   uCalc uc;
   auto ExprT = uc.ExpressionTransformer();  // Transformer used for Eval() and Evaluate()

   auto p1 = ExprT.FromTo("AddUp({x})", "{x}"); // RewindOnChange False by default
   auto p2 = ExprT.FromTo("AddUp({x}, {y})", "({x} + AddUp({y}))").SetRewindOnChange(true);

   cout << "p1 RewindOnChange: " << tf(p1.RewindOnChange()) << endl;
   cout << "p2 RewindOnChange: " << tf(p2.RewindOnChange()) << endl;

   cout << "" << endl;

   cout << "Input: " << "AddUp(1,2,3,4)" << endl;
   cout << "Transform: " << ExprT.Transform("AddUp(1,2,3,4)") << endl;
   cout << "Eval: " << uc.Eval("AddUp(1,2,3,4)") << endl;
}
				
			
p1 RewindOnChange: False
p2 RewindOnChange: True

Input: AddUp(1,2,3,4)
Transform: (1 + (2 + (3 + 4)))
Eval: 10
				
					Imports System
Imports uCalcSoftware
Public Module Program
   Public Sub Main()
      Dim uc As New uCalc()
      Dim ExprT = uc.ExpressionTransformer  '// Transformer used for Eval() and Evaluate()
      
      Dim p1 = ExprT.FromTo("AddUp({x})", "{x}") '// RewindOnChange False by default
      Dim p2 = ExprT.FromTo("AddUp({x}, {y})", "({x} + AddUp({y}))").SetRewindOnChange(true)
      
      Console.WriteLine($"p1 RewindOnChange: {p1.RewindOnChange}")
      Console.WriteLine($"p2 RewindOnChange: {p2.RewindOnChange}")
      
      Console.WriteLine("")
      
      Console.WriteLine($"Input: AddUp(1,2,3,4)")
      Console.WriteLine($"Transform: {ExprT.Transform("AddUp(1,2,3,4)")}")
      Console.WriteLine($"Eval: {uc.Eval("AddUp(1,2,3,4)")}")
   End Sub
End Module
				
			
p1 RewindOnChange: False
p2 RewindOnChange: True

Input: AddUp(1,2,3,4)
Transform: (1 + (2 + (3 + 4)))
Eval: 10
RewindOnChange
				
					using uCalcSoftware;

var uc = new uCalc();
var ExprT = uc.ExpressionTransformer;  // Transformer used for Eval() and Evaluate()

ExprT.DefaultRuleSet.RewindOnChange = true;

ExprT.FromTo("AddUp({x})", "{x}");
ExprT.FromTo("AddUp({x}, {y})", "({x} + AddUp({y}))");

ExprT.FromTo("ArgCount({x})", "1");
ExprT.FromTo("ArgCount({x}, {y})", "(1 + ArgCount({y}))");

ExprT.FromTo("Average({x}, {y})", "AddUp({x}, {y}) / ArgCount({x}, {y})");

var Expression = "Average(1, 2, 3, 4)";
Console.WriteLine($"Input: {Expression}");
Console.WriteLine($"Transform: {ExprT.Transform(Expression)}");
Console.WriteLine($"Eval: {uc.Eval(Expression)}");
				
			
Input: Average(1, 2, 3, 4)
Transform: (1 + (2 + (3 + 4))) / (1 + (1 + (1 + 1)))
Eval: 2.5
				
					#include <iostream>
#include "uCalc.h"

using namespace std;
using namespace uCalcSoftware;

int main() {
   uCalc uc;
   auto ExprT = uc.ExpressionTransformer();  // Transformer used for Eval() and Evaluate()

   ExprT.DefaultRuleSet().RewindOnChange(true);

   ExprT.FromTo("AddUp({x})", "{x}");
   ExprT.FromTo("AddUp({x}, {y})", "({x} + AddUp({y}))");

   ExprT.FromTo("ArgCount({x})", "1");
   ExprT.FromTo("ArgCount({x}, {y})", "(1 + ArgCount({y}))");

   ExprT.FromTo("Average({x}, {y})", "AddUp({x}, {y}) / ArgCount({x}, {y})");

   auto Expression = "Average(1, 2, 3, 4)";
   cout << "Input: " << Expression << endl;
   cout << "Transform: " << ExprT.Transform(Expression) << endl;
   cout << "Eval: " << uc.Eval(Expression) << endl;
}
				
			
Input: Average(1, 2, 3, 4)
Transform: (1 + (2 + (3 + 4))) / (1 + (1 + (1 + 1)))
Eval: 2.5
				
					Imports System
Imports uCalcSoftware
Public Module Program
   Public Sub Main()
      Dim uc As New uCalc()
      Dim ExprT = uc.ExpressionTransformer  '// Transformer used for Eval() and Evaluate()
      
      ExprT.DefaultRuleSet.RewindOnChange = true
      
      ExprT.FromTo("AddUp({x})", "{x}")
      ExprT.FromTo("AddUp({x}, {y})", "({x} + AddUp({y}))")
      
      ExprT.FromTo("ArgCount({x})", "1")
      ExprT.FromTo("ArgCount({x}, {y})", "(1 + ArgCount({y}))")
      
      ExprT.FromTo("Average({x}, {y})", "AddUp({x}, {y}) / ArgCount({x}, {y})")
      
      Dim Expression = "Average(1, 2, 3, 4)"
      Console.WriteLine($"Input: {Expression}")
      Console.WriteLine($"Transform: {ExprT.Transform(Expression)}")
      Console.WriteLine($"Eval: {uc.Eval(Expression)}")
   End Sub
End Module
				
			
Input: Average(1, 2, 3, 4)
Transform: (1 + (2 + (3 + 4))) / (1 + (1 + (1 + 1)))
Eval: 2.5