Files
stgsharp/StgSharp.RegularAnalysis/Text/RegexAnalyzerFrontEnd.PrefixEmit.cs
T
2026-07-06 18:19:32 +08:00

450 lines
18 KiB
C#

//-----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="RegexAnalyzerFrontEnd.PrefixEmit"
// Project: StgSharp
// AuthorGroup: Nitload
// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace StgSharp.RegularAnalysis.Text
{
public static partial class RegexAnalyzerFrontEnd
{
internal static List<RegexIR> EmitPrefix(
List<RegexIR> irList
)
{
List<RegexIR> origin = irList;
List<RegexIR> result = new(origin.Count);
Span<RegexIR> origin_span = CollectionsMarshal.AsSpan(origin);
int region_name = 1;
EmitPrefix(origin_span, result, 0, 0);
FixLineObservation(result);
return result;
void EmitPrefix(
ReadOnlySpan<RegexIR> region,
List<RegexIR> result,
int currentRegion,
int lineBase
)
{
ReadOnlySpan<RegexIR> emit_window;
int index = 0;
while (index < region.Length)
{
emit_window = region[index..];
int emitResult;
if ((emitResult = TryEmitGroupFindPack(emit_window, result, currentRegion, lineBase)) > 0)
{
index += emitResult;
} else if ((emitResult = TryEmitTryGroupTail(emit_window, result, currentRegion, lineBase)) > 0)
{
index += emitResult;
} else if ((emitResult = TryEmitAltChain(emit_window, result, currentRegion, lineBase)) > 0)
{
index += emitResult;
} else if ((emitResult = TryEmitAltSuccessTail(emit_window, result, currentRegion, lineBase)) > 0)
{
index += emitResult;
} else if ((emitResult = TryEmitAltFailTail(emit_window, result, currentRegion, lineBase)) > 0)
{
index += emitResult;
} else if ((emitResult = TryEmitTryIR(emit_window, result, currentRegion, lineBase)) > 0)
{
index += emitResult;
} else
{
result.Add(CloneIR(emit_window[0], currentRegion));
index++;
}
}
}
int TryEmitTryGroupTail(
ReadOnlySpan<RegexIR> origin,
List<RegexIR> result,
int currentRegion,
int lineBase
)
{
if (origin.Length < 5 || origin[0] is not RegexTryIR _try) {
return -1;
}
int tailOffset = _try.CommandCount + 1;
if (origin.Length < tailOffset + 3 ||
origin[tailOffset] is not RegexConditionIR { SuccessCase: 2, FailCase: 1 } condition ||
origin[tailOffset + 1] is not RegexPopIR { IsSuccess: false } pop ||
origin[tailOffset + 2] is not RegexPackIR { Condition: true } pack) {
return -1;
}
int tryOutputIndex = result.Count;
int emitCount = TryEmitTryIR(
origin,
result,
currentRegion,
lineBase,
foldSingleInstructionTry: false);
if (emitCount <= 0 || result[tryOutputIndex] is not RegexTryIR emittedTry) {
return -1;
}
_ = condition;
int regionId = emittedTry.Prefix.WorkingRegion;
RegexIR prefixedPop = CloneIR(pop, regionId);
ApplyPrefix(
prefixedPop,
regionId,
RegexIRPrefixMask.Prev_Fail | RegexIRPrefixMask.Observe_Region,
regionId);
RegexIR prefixedPack = CloneIR(pack, regionId);
ApplyPrefix(
prefixedPack,
regionId,
RegexIRPrefixMask.Prev_Success | RegexIRPrefixMask.Observe_Region,
regionId);
result.Add(prefixedPop);
result.Add(prefixedPack);
return emitCount + 3;
}
int TryEmitAltChain(
ReadOnlySpan<RegexIR> origin,
List<RegexIR> result,
int currentRegion,
int lineBase
)
{
if (origin.Length < 3 ||
origin[0] is not RegexConditionIR { SuccessCase: 1, FailCase: 2 } ||
origin[1] is not RegexPopIR { IsSuccess: true } ||
origin[2] is not RegexTryIR) {
return -1;
}
int observeRegion = currentRegion;
RegexIRPrefixMask observeMask = RegexIRPrefixMask.Observe_Region;
int observeSource = currentRegion;
if (result.Count > 0)
{
RegexIR previous = result[^1];
observeRegion = GetObservedRegion(previous, currentRegion);
GetObservation(previous, result, lineBase, currentRegion, out observeMask, out observeSource);
ApplyPrefix(previous, observeRegion, RegexIRPrefixMask.Pop_Success, observeRegion);
}
RegexIRPrefixMask mask = RegexIRPrefixMask.Prev_Fail | observeMask;
int emit_count = TryEmitTryIR(origin[2..], result, currentRegion, lineBase, mask, observeSource, observeRegion);
return emit_count < 0 ? -1 : emit_count + 2;
}
int TryEmitAltSuccessTail(
ReadOnlySpan<RegexIR> origin,
List<RegexIR> result,
int currentRegion,
int lineBase
)
{
if (origin.Length < 2 ||
origin[0] is not RegexConditionIR { SuccessCase: 1, FailCase: 2 } ||
origin[1] is not RegexPopIR { IsSuccess: true } ||
result.Count == 0) {
return -1;
}
RegexIR previous = result[^1];
int observeRegion = GetObservedRegion(previous, currentRegion);
ApplyPrefix(previous, observeRegion, RegexIRPrefixMask.Pop_Success, observeRegion);
return 2;
}
int TryEmitAltFailTail(
ReadOnlySpan<RegexIR> origin,
List<RegexIR> result,
int currentRegion,
int lineBase
)
{
if (origin.Length < 1 ||
origin[0] is not RegexPopIR { IsSuccess: false } pop ||
result.Count == 0) {
return -1;
}
RegexIR previous = result[^1];
if ((previous.Prefix.Mask & RegexIRPrefixMask.Pop_Success) == 0) {
return -1;
}
GetObservation(
previous,
result,
lineBase,
currentRegion,
out RegexIRPrefixMask observeMask,
out int observeSource);
RegexIR prefixedPop = CloneIR(pop, currentRegion);
ApplyPrefix(
prefixedPop,
currentRegion,
RegexIRPrefixMask.Prev_Fail | observeMask,
observeSource);
result.Add(prefixedPop);
return 1;
}
// convert TRY into region
int TryEmitTryIR(
ReadOnlySpan<RegexIR> origin,
List<RegexIR> result,
int currentRegion,
int lineBase,
RegexIRPrefixMask mask = 0,
int observeSource = 0,
int reuseRegion = 0,
bool foldSingleInstructionTry = true
)
{
if (origin.Length == 0 || origin[0] is not RegexTryIR _try) {
return -1;
}
int commandCount = _try.CommandCount;
if (origin.Length < commandCount + 1) {
return -1;
}
int regionId = reuseRegion == 0 ? region_name : reuseRegion;
if (reuseRegion == 0) {
region_name++;
}
if (foldSingleInstructionTry &&
commandCount == 1 &&
IsFoldableSingleTryInstruction(origin[1])) {
RegexIR folded = CloneIR(origin[1], regionId);
int foldedObserveSource =
(mask & RegexIRPrefixMask.ObserveMethod) != 0 ?
observeSource :
_try.Prefix.ObserveSource;
folded.Prefix = new RegexIRPrefix(
regionId,
folded.Prefix.Mask | _try.Prefix.Mask | mask,
foldedObserveSource);
result.Add(folded);
return commandCount + 1;
}
List<RegexIR> sub_ir = new(commandCount);
ReadOnlySpan<RegexIR> try_region = origin.Slice(1, commandCount);
EmitPrefix(try_region, sub_ir, regionId, lineBase + result.Count + 1);
RegexTryIR newTry = new(sub_ir.Count)
{
IsFullyOptimized = _try.IsFullyOptimized,
Prefix = new RegexIRPrefix(regionId, _try.Prefix.Mask | mask, observeSource)
};
result.Add(newTry);
result.AddRange(sub_ir);
return commandCount + 1;
}
int TryEmitGroupFindPack(
ReadOnlySpan<RegexIR> origin,
List<RegexIR> result,
int currentRegion,
int lineBase
)
{
if (origin.Length < 5 ||
origin[0] is not RegexTryIR { CommandCount: 1 } _try ||
origin[1] is not RegexFindComplexIR find ||
origin[2] is not RegexConditionIR { SuccessCase: 2, FailCase: 1 } ||
origin[3] is not RegexPopIR { IsSuccess: false } ||
origin[4] is not RegexPackIR { Condition: true } pack) {
return -1;
}
int regionId = region_name;
region_name++;
RegexIRPrefixMask findMask = find.Prefix.Mask | _try.Prefix.Mask;
int findObserveSource =
(find.Prefix.Mask & RegexIRPrefixMask.ObserveMethod) != 0 ?
find.Prefix.ObserveSource :
_try.Prefix.ObserveSource;
RegexFindComplexIR newFind = new(find.IsNearest, find.CommandCount)
{
IsFullyOptimized = find.IsFullyOptimized,
Prefix = new RegexIRPrefix(regionId, findMask, findObserveSource)
};
RegexPackIR newPack = new(pack.Condition, pack.GroupName)
{
Prefix = new RegexIRPrefix(
regionId,
pack.Prefix.Mask | RegexIRPrefixMask.Prev_Success | RegexIRPrefixMask.Observe_Line,
-1)
};
result.Add(newFind);
result.Add(newPack);
return 5;
}
static int GetObservedRegion(
RegexIR ir,
int currentRegion
)
{
return ir.Prefix.WorkingRegion == 0 ? currentRegion : ir.Prefix.WorkingRegion;
}
static void GetObservation(
RegexIR ir,
List<RegexIR> result,
int lineBase,
int currentRegion,
out RegexIRPrefixMask observeMask,
out int observeSource
)
{
if (IsLineObservable(ir))
{
observeMask = RegexIRPrefixMask.Observe_Line;
observeSource = -1;
} else
{
observeMask = RegexIRPrefixMask.Observe_Region;
observeSource = GetObservedRegion(ir, currentRegion);
}
}
static void FixLineObservation(
List<RegexIR> result
)
{
for (int i = 0; i < result.Count; i++)
{
RegexIRPrefix prefix = result[i].Prefix;
if ((prefix.Mask & RegexIRPrefixMask.Observe_Line) == 0) {
continue;
}
result[i].Prefix = new RegexIRPrefix(
prefix.WorkingRegion,
prefix.Mask,
i - 1);
}
}
static bool IsFoldableSingleTryInstruction(
RegexIR ir
)
{
return IsLineObservable(ir) &&
(ir.Prefix.Mask & RegexIRPrefixMask.ExeCondition) == 0 &&
(ir.Prefix.Mask & RegexIRPrefixMask.ObserveMethod) == 0;
}
static bool IsLineObservable(
RegexIR ir
)
{
return ir is RegexMatchSeqIR or
RegexMatchSetIR or
RegexCountSeqIR or
RegexCountSetIR or
RegexFindSeqIR or
RegexFindComplexIR;
}
static void ApplyPrefix(
RegexIR ir,
int workingRegion,
RegexIRPrefixMask mask,
int observeSource
)
{
ir.Prefix = new RegexIRPrefix(
workingRegion,
ir.Prefix.Mask | mask,
observeSource);
}
static RegexIR CloneIR(
RegexIR ir,
int currentRegion
)
{
RegexIR clone = ir switch
{
RegexFindComplexIR find => new RegexFindComplexIR(find.IsNearest, find.CommandCount)
{
IsFullyOptimized = find.IsFullyOptimized
},
RegexFindSeqIR find => new RegexFindSeqIR(find.Pattern, find.IsFirst),
RegexMatchSeqIR match => new RegexMatchSeqIR(match.Pattern),
RegexMatchSetIR match => new RegexMatchSetIR(match.Charset),
RegexConditionIR condition => new RegexConditionIR(condition.SuccessCase, condition.FailCase),
RegexPackIR pack => new RegexPackIR(pack.Condition, pack.GroupName),
RegexCountComplexIR count => new RegexCountComplexIR(count.Min, count.Max, count.IsGreedy, count.CommandCount)
{
IsFullyOptimized = count.IsFullyOptimized
},
RegexCountSeqIR count => new RegexCountSeqIR(count.Min, count.Max, count.IsGreedy, count.Sequence),
RegexCountSetIR count => new RegexCountSetIR(count.Min, count.Max, count.IsGreedy, count.CharSet),
RegexTryIR tryIR => new RegexTryIR(tryIR.CommandCount)
{
IsFullyOptimized = tryIR.IsFullyOptimized
},
RegexPopIR pop => new RegexPopIR(pop.IsSuccess),
_ => throw new NotSupportedException($"Unsupported IR type: {ir.GetType().Name}")
};
clone.Prefix = new RegexIRPrefix(
ir.Prefix.WorkingRegion == 0 ? currentRegion : ir.Prefix.WorkingRegion,
ir.Prefix.Mask,
ir.Prefix.ObserveSource);
return clone;
}
}
}
}