}
else
{
- // If this node is nullable for the given context then prune any branches that are less preferred than
- // just the empty match. This is done in order to maintain backtracking semantics.
- SymbolicRegexNode<TSet> node = IsNullableFor(context) ? PruneLowerPriorityThanNullability(builder, context) : this;
+ // To maintain backtracking semantics, prune any branches that are less preferred than just the empty match
+ SymbolicRegexNode<TSet> node = PruneLowerPriorityThanNullability(builder, context);
return node.CreateDerivative(builder, elem, context);
}
}
/// <summary>Prune this node wrt the given context in order to maintain backtracking semantics. Mimics how backtracking chooses a path.</summary>
private SymbolicRegexNode<TSet> PruneLowerPriorityThanNullability(SymbolicRegexBuilder<TSet> builder, uint context)
{
- //caching pruning to avoid otherwise potential quadratic worst case behavior
+ if (!IsNullableFor(context))
+ return this;
+
+ // Cache result to avoid otherwise potential quadratic worst case behavior
SymbolicRegexNode<TSet>? prunedNode;
(SymbolicRegexNode<TSet>, uint) key = (this, context);
if (builder._pruneLowerPriorityThanNullabilityCache.TryGetValue(key, out prunedNode))
CreateConcat(builder, _left.PruneLowerPriorityThanNullability(builder, context), _right.PruneLowerPriorityThanNullability(builder, context));
break;
- case SymbolicRegexNodeKind.Loop when _info.IsLazyLoop && _lower == 0:
- //lazy nullable loop reduces to (), i.e., the loop body is just forgotten
- prunedNode = builder.Epsilon;
+ case SymbolicRegexNodeKind.Loop:
+ Debug.Assert(_left is not null);
+ // Lazy nullable loop reduces to (), i.e., the loop body is just forgotten
+ prunedNode = _info.IsLazyLoop && _lower == 0 ? builder.Epsilon :
+ CreateLoop(builder, _left.PruneLowerPriorityThanNullability(builder, context), _lower, _upper, _info.IsLazyLoop);
break;
case SymbolicRegexNodeKind.Effect:
- //Effects are maintained and the pruning is propagated to the body of the effect
+ // Effects are maintained and the pruning is propagated to the body of the effect
Debug.Assert(_left is not null && _right is not null);
prunedNode = CreateEffect(builder, _left.PruneLowerPriorityThanNullability(builder, context), _right);
break;
default:
- //In all other remaining cases no pruning takes place
+ // In all other remaining cases no pruning takes place
prunedNode = this;
break;
}
yield return new object[] { engine, @"(?m)$[a-z]", "abc\ndef\nxyz", new ValueTuple<int, int>[] { } };
yield return new object[] { engine, @"(?m)^$", "", new[] { (0, 0) } };
yield return new object[] { engine, @"(?m)(?:^$)*", "a\nb\nc", new[] { (0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5) } };
- if (!RegexHelpers.IsNonBacktracking(engine)) // https://github.com/dotnet/runtime/issues/72470
- {
- yield return new object[] { engine, @"(?m)(?:^|a)+", "a\naaa\n", new[] { (0, 0), (2, 2), (3, 5), (6, 6) } };
- yield return new object[] { engine, @"(?m)(?:^|a)*", "a\naaa\n", new[] { (0, 0), (1, 1), (2, 2), (3, 5), (5, 5), (6, 6) } };
- }
+ yield return new object[] { engine, @"(?m)(?:^|a)+", "a\naaa\n", new[] { (0, 0), (2, 2), (3, 5), (6, 6) } };
+ yield return new object[] { engine, @"(?m)(?:^|a)*", "a\naaa\n", new[] { (0, 0), (1, 1), (2, 2), (3, 5), (5, 5), (6, 6) } };
yield return new object[] { engine, @"(?m)(?:^[a-z])+", "abc\ndef\nxyz", new[] { (0, 1), (4, 5), (8, 9) } };
yield return new object[] { engine, @"(?m)(?:^[a-z]{3}\n?)+", "abc\ndef\nxyz", new[] { (0, 11) } };
yield return new object[] { engine, @"(?m)(?:^[a-z]{3}\n?)*", "abc\ndef\nxyz", new[] { (0, 11), (11, 11) } };