switch root namespace name to NITLOAD
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegexAnalyzer.IRGenerate"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Nitload.RegularAnalysis.Abstraction;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Design;
|
||||
using System.Data;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
|
||||
namespace Nitload.RegularAnalysis.Text
|
||||
{
|
||||
public partial class RegexAnalyzer
|
||||
{
|
||||
|
||||
internal static List<RegexIR> GenerateIR(
|
||||
AbstractSyntaxTree<RegexAstNode, RegexElementLabel> tree,
|
||||
ref RegexInfo info
|
||||
)
|
||||
{
|
||||
List<RegexIR> list = GenerateIRRecursion(tree.Root, 0, [], out int length).ToList();
|
||||
info.MinPredictLength = length;
|
||||
return list;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void EmitCount(
|
||||
int curDepth,
|
||||
RegexIRGenerator ir,
|
||||
int min,
|
||||
int max,
|
||||
bool isGreedy,
|
||||
RegexAstNode right,
|
||||
HashSet<string> group_names,
|
||||
out int minLength
|
||||
)
|
||||
{
|
||||
bool is_operator = !RegexAstNode.IsNullOrEmpty(right) &&
|
||||
(right.Source.Flag & RegexElementLabel.VAST_OPERATOR) != 0;
|
||||
|
||||
if (is_operator) // seq to be count is complex
|
||||
{
|
||||
RegexIRGenerator right_ir = GenerateIRRecursion(right, curDepth + 1, group_names,
|
||||
out minLength);
|
||||
_ = ir.EmitCountComplex(min, max, isGreedy, right_ir.Count);
|
||||
_ = ir.EmitIRStream(right_ir);
|
||||
} else
|
||||
{
|
||||
RegexElementLabel count_source_mask = right.Source.Flag;
|
||||
switch (count_source_mask)
|
||||
{
|
||||
case RegexElementLabel.UNIT:
|
||||
minLength = 1;
|
||||
_ = ir.EmitCountSeq(min, max, isGreedy, right.Value);
|
||||
break;
|
||||
case RegexElementLabel.UNIT_SET:
|
||||
minLength = 1;
|
||||
_ = ir.EmitCountSet(min, max, isGreedy, right.Value);
|
||||
break;
|
||||
case RegexElementLabel.UNIT_SPAN:
|
||||
minLength = 1;
|
||||
_ = ir.EmitCountSeq(min, max, isGreedy, right.Value);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unexpected regex element type for count operator: {count_source_mask}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static RegexIRGenerator GenerateIRRecursion(
|
||||
RegexAstNode node,
|
||||
int depth,
|
||||
HashSet<string> group_names,
|
||||
out int minLength
|
||||
)
|
||||
{
|
||||
#pragma warning disable IDE0010
|
||||
if (depth > 1024)
|
||||
{
|
||||
throw new OverflowException("Regex parse exceeds maximum recursion depth");
|
||||
}
|
||||
int curDepth = depth;
|
||||
RegexAstNode left = node.Left;
|
||||
RegexAstNode right = node.Right;
|
||||
RegexIRGenerator leftIR;
|
||||
RegexIRGenerator rightIR;
|
||||
|
||||
Token<RegexElementLabel> token = node.Source;
|
||||
|
||||
HashSet<string> gn = group_names ?? [];
|
||||
|
||||
switch (node.Source.Flag)
|
||||
{
|
||||
case RegexElementLabel.GROUP_BEGIN:
|
||||
string group_source = token.Value;
|
||||
|
||||
// process group type
|
||||
bool has_name = group_source.StartsWith(@"(?<", StringComparison.Ordinal); // simple group: (pattern)
|
||||
string group_name = has_name ?
|
||||
group_source[3..^1] : /* named group (?<name>) */
|
||||
string.Empty; /* unnamed group (?:) */
|
||||
|
||||
if (has_name)
|
||||
{
|
||||
if (!gn.Add(group_name)) {
|
||||
throw new InvalidOperationException($"Duplicate group name: {group_name}");
|
||||
}
|
||||
}
|
||||
|
||||
RegexIRGenerator groupIR = new();
|
||||
rightIR = GenerateIRRecursion(right, curDepth + 1, gn, out minLength);
|
||||
_ = groupIR.EmitTry(rightIR.Count)
|
||||
.EmitIRStream(rightIR)
|
||||
.EmitCondition(2, 1)
|
||||
.EmitPop(false)
|
||||
.EmitPack(true, group_name);
|
||||
return groupIR;
|
||||
case RegexElementLabel.COUNT:
|
||||
string count_source = token.Value;
|
||||
bool isGreedy = !(count_source.Length > 1 && count_source.EndsWith('?'));
|
||||
RegexAstNode seq = right;
|
||||
ReadOnlySpan<char> count_source_span = count_source;
|
||||
RegexIRGenerator count_ir = new();
|
||||
int length_once;
|
||||
if (count_source_span[0] == '*') // infinite count
|
||||
{
|
||||
EmitCount(curDepth, count_ir, 0, -1, isGreedy, seq, gn, out _);
|
||||
minLength = 0;
|
||||
} else if (count_source_span[0] == '+') // at least one
|
||||
{
|
||||
EmitCount(curDepth, count_ir, 1, -1, isGreedy, seq, gn, out length_once);
|
||||
minLength = length_once;
|
||||
} else if (count_source_span[0] == '?') // zero or one
|
||||
{
|
||||
EmitCount(curDepth, count_ir, 0, 1, isGreedy, seq, gn, out _);
|
||||
minLength = 0;
|
||||
} else if (count_source_span[0] == '{') // zero or one, non-greedy
|
||||
{
|
||||
int comma_pos = count_source_span.IndexOf(',');
|
||||
if (comma_pos == -1)
|
||||
{
|
||||
if (int.TryParse(count_source[1..(isGreedy ? ^2 : ^1)], out int count))
|
||||
{
|
||||
EmitCount(curDepth, count_ir, count, count, isGreedy, seq, gn,
|
||||
out length_once);
|
||||
minLength = length_once * count;
|
||||
} else
|
||||
{
|
||||
throw new InvalidCastException($"Invalid count format: {count_source}");
|
||||
}
|
||||
} else
|
||||
{
|
||||
if (int.TryParse(count_source[1..comma_pos], out int min) &&
|
||||
int.TryParse(count_source[(comma_pos + 1)..(isGreedy ? ^1 : ^2)], out int max))
|
||||
{
|
||||
EmitCount(curDepth, count_ir, min, max, isGreedy, seq, gn,
|
||||
out length_once);
|
||||
minLength = length_once * min;
|
||||
} else
|
||||
{
|
||||
throw new InvalidCastException($"Invalid count format: {count_source}");
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
} // unknown count format
|
||||
|
||||
return count_ir;
|
||||
case RegexElementLabel.CONCAT:
|
||||
RegexIRGenerator concat = new();
|
||||
|
||||
// process left node
|
||||
int left_length = 0;
|
||||
if (RegexAstNode.IsNullOrEmpty(left))
|
||||
{
|
||||
// do nothing
|
||||
} else if ((left.Source.Flag & RegexElementLabel.SEQUENCE) != 0)
|
||||
{
|
||||
switch (left.Source.Flag)
|
||||
{
|
||||
case RegexElementLabel.UNIT_SPAN:
|
||||
left_length = left.Value.Length;
|
||||
_ = concat.EmitMatchSeq(left.Value);
|
||||
break;
|
||||
case RegexElementLabel.UNIT:
|
||||
left_length = 1;
|
||||
_ = concat.EmitMatchSeq(left.Value);
|
||||
break;
|
||||
case RegexElementLabel.UNIT_SET:
|
||||
left_length = 1;
|
||||
_ = concat.EmitMatchSet(left.Value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if ((left.Source.Flag & RegexElementLabel.VAST_OPERATOR) != 0)
|
||||
{
|
||||
leftIR = GenerateIRRecursion(left, curDepth + 1, gn, out left_length);
|
||||
_ = concat.EmitIRStream(leftIR);
|
||||
}
|
||||
|
||||
// process right node
|
||||
int right_length = 0;
|
||||
if (RegexAstNode.IsNullOrEmpty(right))
|
||||
{
|
||||
// do nothing
|
||||
} else if ((right.Source.Flag & RegexElementLabel.SEQUENCE) != 0)
|
||||
{
|
||||
switch (right.Source.Flag)
|
||||
{
|
||||
case RegexElementLabel.UNIT_SPAN:
|
||||
right_length = right.Value.Length;
|
||||
_ = concat.EmitMatchSeq(right.Value);
|
||||
break;
|
||||
case RegexElementLabel.UNIT:
|
||||
right_length = 1;
|
||||
_ = concat.EmitMatchSeq(right.Value);
|
||||
break;
|
||||
case RegexElementLabel.UNIT_SET:
|
||||
right_length = 1;
|
||||
_ = concat.EmitMatchSet(right.Value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if ((right.Source.Flag & RegexElementLabel.VAST_OPERATOR) != 0)
|
||||
{
|
||||
rightIR = GenerateIRRecursion(right, curDepth + 1, gn, out right_length);
|
||||
_ = concat.EmitIRStream(rightIR);
|
||||
}
|
||||
minLength = left_length + right_length;
|
||||
return concat;
|
||||
case RegexElementLabel.ALT:
|
||||
RegexIRGenerator alt = new();
|
||||
List<RegexIRGenerator> casesIRList = [];
|
||||
RegexAstNode cur_node = right;
|
||||
int ir_count = 0;
|
||||
int min_case_length = int.MaxValue;
|
||||
while (!RegexAstNode.IsNullOrEmpty(cur_node))
|
||||
{
|
||||
RegexIRGenerator _case = GenerateIRRecursion(cur_node, curDepth + 1, gn,
|
||||
out int case_length);
|
||||
min_case_length = int.Min(case_length, min_case_length);
|
||||
casesIRList.Add(_case);
|
||||
ir_count += _case.Count;
|
||||
cur_node = cur_node.Next;
|
||||
}
|
||||
int case_count = casesIRList.Count;
|
||||
while (case_count != 0)
|
||||
{
|
||||
RegexIRGenerator cur_ir = casesIRList[^case_count];
|
||||
ir_count -= cur_ir.Count;
|
||||
case_count--;
|
||||
_ = alt.EmitTry(cur_ir.Count)
|
||||
.EmitIRStream(cur_ir)
|
||||
.EmitCondition(1/* need to calc here */, 2)
|
||||
.EmitPop(true);
|
||||
}
|
||||
_ = alt.EmitPop(false);
|
||||
minLength = min_case_length;
|
||||
return alt;
|
||||
case RegexElementLabel.UNIT_SPAN:
|
||||
RegexIRGenerator unit = new();
|
||||
_ = unit.EmitMatchSeq(node.Value);
|
||||
minLength = node.Value.Length;
|
||||
return unit;
|
||||
case RegexElementLabel.UNIT:
|
||||
RegexIRGenerator unit_seq = new();
|
||||
_ = unit_seq.EmitMatchSeq(node.Value);
|
||||
minLength = 1;
|
||||
return unit_seq;
|
||||
case RegexElementLabel.UNIT_SET:
|
||||
RegexIRGenerator unit_set = new();
|
||||
_ = unit_set.EmitMatchSet(node.Value);
|
||||
minLength = 1;
|
||||
return unit_set;
|
||||
default:
|
||||
minLength = 0;
|
||||
return null!;
|
||||
}
|
||||
#pragma warning restore IDE0010
|
||||
|
||||
|
||||
// info.MinPredictLength = ComputeMinLength(tree.Root);
|
||||
|
||||
static int ComputeMinLength(
|
||||
RegexAstNode node
|
||||
)
|
||||
{
|
||||
if (node.EqualityTypeConvert is RegexElementLabel.UNIT_SPAN or
|
||||
RegexElementLabel.UNIT)
|
||||
{
|
||||
return node.Value.Length;
|
||||
} else if (node.EqualityTypeConvert == RegexElementLabel.CONCAT)
|
||||
{
|
||||
return ComputeMinLength(node.Left) + ComputeMinLength(node.Right);
|
||||
} else if (node.EqualityTypeConvert == RegexElementLabel.ALT)
|
||||
{
|
||||
RegexAstNode case_0 = node.Right;
|
||||
int length = int.MaxValue;
|
||||
do
|
||||
{
|
||||
length = int.Min(ComputeMinLength(case_0), length);
|
||||
case_0 = case_0.Next;
|
||||
} while (!RegexAstNode.IsNullOrEmpty(case_0));
|
||||
return length;
|
||||
} else if (node.EqualityTypeConvert == RegexElementLabel.COUNT)
|
||||
{
|
||||
string source = node.Source.Value;
|
||||
int single_count = ComputeMinLength(node.Right);
|
||||
return 0;
|
||||
} else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,493 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegexAnalyzer.IROptimize"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Nitload.Common.Collections;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nitload.RegularAnalysis.Text
|
||||
{
|
||||
public partial class RegexAnalyzer
|
||||
{
|
||||
|
||||
internal static void ScanAndOptimizeIR(
|
||||
List<RegexIR> baseIR,
|
||||
out List<RegexIR> optIR
|
||||
)
|
||||
{
|
||||
int opt_count;
|
||||
List<RegexIR> origin_ir = baseIR;
|
||||
List<RegexIR> opt_ir = new(origin_ir.Count);
|
||||
RegexIR[] opt_window = new RegexIR[8];
|
||||
Span<RegexIR> opt_win_sp = opt_window;
|
||||
int result;
|
||||
|
||||
// phase 0: common opt
|
||||
phase_zero:
|
||||
opt_count = 0;
|
||||
int index = 0;
|
||||
Span<RegexIR> origin_span = CollectionsMarshal.AsSpan(origin_ir);
|
||||
|
||||
do
|
||||
{
|
||||
int process_count = 0;
|
||||
int last = int.Min(origin_span.Length, index + 8);
|
||||
opt_win_sp.Clear();
|
||||
origin_span[index..last].CopyTo(opt_win_sp);
|
||||
|
||||
if ((result = TryExpandPlus(opt_win_sp, opt_ir)) >= 0)
|
||||
{
|
||||
process_count++;
|
||||
if (result > 0)
|
||||
{
|
||||
RegexCountComplexIR cc = Unsafe.As<RegexCountComplexIR>(opt_win_sp[0]);
|
||||
Span<RegexIR> sp = origin_span.Slice(index + 1, result);
|
||||
TryExpandComplexPlus(cc.IsGreedy, sp, opt_ir);
|
||||
process_count += result;
|
||||
}
|
||||
opt_count++;
|
||||
} else if (TryOptDotCountCount(opt_win_sp, opt_ir) >= 0)
|
||||
{
|
||||
process_count += 2;
|
||||
opt_count++;
|
||||
} else if (TryFlattenCount(opt_win_sp, opt_ir) >= 0)
|
||||
{
|
||||
process_count++;
|
||||
opt_count++;
|
||||
} else if ((result = TryExpandOnce(opt_win_sp, opt_ir)) >= 0)
|
||||
{
|
||||
process_count++;
|
||||
if (result > 0)
|
||||
{
|
||||
RegexCountComplexIR cc = Unsafe.As<RegexCountComplexIR>(opt_win_sp[0]);
|
||||
Span<RegexIR> sp = origin_span.Slice(index + 1, result);
|
||||
TryExpandComplex(sp, opt_ir, true);
|
||||
process_count += result;
|
||||
}
|
||||
opt_count++;
|
||||
} else if (opt_win_sp[0] is IRegexComplex { IsFullyOptimized: true } skip)
|
||||
{
|
||||
int count = skip.CommandCount;
|
||||
process_count += count + 1;
|
||||
Span<RegexIR> opt_block = origin_span.Slice(index, count + 1);
|
||||
opt_ir.AddRange(opt_block);
|
||||
} else if (opt_win_sp[0] is RegexTryIR { IsFullyOptimized: false } try_ir)
|
||||
{
|
||||
process_count++;
|
||||
int cmd_cnt = try_ir.CommandCount;
|
||||
Span<RegexIR> try_sp = origin_span.Slice(index + 1, cmd_cnt);
|
||||
ScanAndOptimizeIR(try_sp, out List<RegexIR> opt_try);
|
||||
RegexTryIR new_try = new(opt_try.Count)
|
||||
{
|
||||
IsFullyOptimized = true
|
||||
};
|
||||
opt_ir.Add(new_try);
|
||||
opt_ir.AddRange(opt_try);
|
||||
process_count += cmd_cnt;
|
||||
opt_count++;
|
||||
} else
|
||||
{
|
||||
opt_ir.Add(opt_win_sp[0]);
|
||||
process_count++;
|
||||
}
|
||||
|
||||
index += process_count;
|
||||
} while (index < origin_ir.Count);
|
||||
if (opt_count > 0)
|
||||
{
|
||||
// need to swap the origin_ir and opt_ir, and clear the opt_ir for next round
|
||||
if (ReferenceEquals(origin_ir, baseIR))
|
||||
{
|
||||
origin_ir = new(opt_ir.Count);
|
||||
}
|
||||
(origin_ir, opt_ir) = (opt_ir, origin_ir);
|
||||
opt_ir.Clear();
|
||||
goto phase_zero;
|
||||
}
|
||||
|
||||
// phase 1: dot star replace
|
||||
(origin_ir, opt_ir) = (opt_ir, origin_ir);
|
||||
opt_ir.Clear();
|
||||
index = 0;
|
||||
opt_count = 0;
|
||||
origin_span = CollectionsMarshal.AsSpan(origin_ir);
|
||||
do
|
||||
{
|
||||
int process_count = 0;
|
||||
int last = int.Min(origin_span.Length, index + 8);
|
||||
opt_win_sp.Clear();
|
||||
origin_span[index..last].CopyTo(opt_win_sp);
|
||||
|
||||
if (opt_win_sp[0] is IRegexComplex { IsFullyOptimized: true } skip)
|
||||
{
|
||||
int count = skip.CommandCount;
|
||||
process_count += count + 1;
|
||||
Span<RegexIR> opt_block = origin_span.Slice(index, count + 1);
|
||||
opt_ir.AddRange(opt_block);
|
||||
} else
|
||||
if (TryReplaceDotStar(opt_win_sp, opt_ir) >= 0)
|
||||
{
|
||||
opt_count++;
|
||||
process_count++;
|
||||
} else
|
||||
{
|
||||
opt_ir.Add(opt_win_sp[0]);
|
||||
process_count++;
|
||||
}
|
||||
|
||||
index += process_count;
|
||||
} while (index < origin_ir.Count);
|
||||
if (opt_count == 0)
|
||||
{
|
||||
optIR = opt_ir;
|
||||
return;
|
||||
}
|
||||
|
||||
// phase 2: find-1 merge and try-find-1 rotate
|
||||
do
|
||||
{
|
||||
(origin_ir, opt_ir) = (opt_ir, origin_ir);
|
||||
opt_ir.Clear();
|
||||
opt_count = 0;
|
||||
index = 0;
|
||||
origin_span = CollectionsMarshal.AsSpan(origin_ir);
|
||||
do
|
||||
{
|
||||
int process_count = 0;
|
||||
int last = int.Min(origin_span.Length, index + 8);
|
||||
opt_win_sp.Clear();
|
||||
origin_span[index..last].CopyTo(opt_win_sp);
|
||||
|
||||
if (opt_win_sp[0] is IRegexComplex { IsFullyOptimized: true, CommandCount: > 0 } skip)
|
||||
{
|
||||
int count = skip.CommandCount;
|
||||
process_count += count + 1;
|
||||
Span<RegexIR> opt_block = origin_span.Slice(index, count + 1);
|
||||
opt_ir.AddRange(opt_block);
|
||||
} else if (TyrMergeFindRestTwice(opt_win_sp, opt_ir) == 0)
|
||||
{
|
||||
process_count += 2;
|
||||
opt_count++;
|
||||
} else if ((result = TyrMergeFindRestTry(opt_win_sp, opt_ir)) >= 0)
|
||||
{
|
||||
Span<RegexIR> opt_block = origin_span.Slice(index + 2, result);
|
||||
opt_ir.AddRange(opt_block);
|
||||
process_count += 2;
|
||||
process_count += result;
|
||||
opt_count++;
|
||||
} else
|
||||
{
|
||||
opt_ir.Add(opt_win_sp[0]);
|
||||
process_count++;
|
||||
}
|
||||
|
||||
index += process_count;
|
||||
} while (index < origin_ir.Count);
|
||||
} while (opt_count > 0);
|
||||
optIR = opt_ir;
|
||||
}
|
||||
|
||||
private static void ScanAndOptimizeIR(
|
||||
Span<RegexIR> baseIR,
|
||||
out List<RegexIR> opt_ir
|
||||
)
|
||||
{
|
||||
List<RegexIR> originList = new(baseIR.Length);
|
||||
CollectionsMarshal.SetCount(originList, baseIR.Length);
|
||||
baseIR.CopyTo(CollectionsMarshal.AsSpan(originList));
|
||||
ScanAndOptimizeIR(originList, out opt_ir);
|
||||
}
|
||||
|
||||
private static int TryFlattenCount(
|
||||
Span<RegexIR> window,
|
||||
List<RegexIR> opt
|
||||
)
|
||||
{
|
||||
const int flatten_size = 32;
|
||||
RegexIR ir = window[0];
|
||||
if (ir is not RegexCountSeqIR seq) {
|
||||
return -1;
|
||||
}
|
||||
int min = seq.Min;
|
||||
int max = seq.Max;
|
||||
if (min == 0) {
|
||||
return -1;
|
||||
}
|
||||
string pattern = seq.Pattern;
|
||||
int pattern_size = pattern.Length;
|
||||
if (pattern_size > flatten_size) {
|
||||
opt.Add(ir);
|
||||
}
|
||||
int min_expand = flatten_size / pattern_size;
|
||||
min_expand = int.Min(min_expand, min);
|
||||
string expand = string.Concat(Enumerable.Repeat(pattern, min_expand));
|
||||
int new_min = min / min_expand;
|
||||
int new_rest = min % min_expand;
|
||||
if (new_min == 1)
|
||||
{
|
||||
opt.Add(new RegexMatchSeqIR(expand));
|
||||
} else
|
||||
{
|
||||
opt.Add(new RegexCountSeqIR(new_min, new_min, seq.IsGreedy, expand));
|
||||
}
|
||||
if (new_rest > 0)
|
||||
{
|
||||
string expand_tail = string.Concat(Enumerable.Repeat(pattern, new_rest));
|
||||
opt.Add(new RegexMatchSeqIR(expand_tail));
|
||||
}
|
||||
int rest_count = max - min;
|
||||
rest_count = int.Max(rest_count, -1);
|
||||
opt.Add(new RegexCountSeqIR(0, rest_count, seq.IsGreedy, pattern));
|
||||
return 0;
|
||||
}
|
||||
|
||||
#region dot count count
|
||||
|
||||
private static int TryOptDotCountCount(
|
||||
Span<RegexIR> window,
|
||||
List<RegexIR> opt
|
||||
)
|
||||
{
|
||||
/* .* <any>*
|
||||
* convert to
|
||||
* .*
|
||||
*/
|
||||
RegexIR cur_ir = window[0];
|
||||
if (cur_ir is not RegexCountSetIR { IsDotStar: true } dot) {
|
||||
return -1;
|
||||
}
|
||||
if (window[1] is not IRegexCount { IsStar: true } after) {
|
||||
return -1;
|
||||
}
|
||||
if (dot.IsGreedy)
|
||||
{
|
||||
opt.Add(window[0]);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static int TryReplaceDotStar(
|
||||
Span<RegexIR> window,
|
||||
List<RegexIR> opt
|
||||
)
|
||||
{
|
||||
if (window[0] is not RegexCountSetIR { IsDotStar: true } dot) {
|
||||
return -1;
|
||||
}
|
||||
if (dot.IsGreedy)
|
||||
{
|
||||
RegexFindComplexIR find = new(false, -1)
|
||||
{
|
||||
IsFullyOptimized = true
|
||||
};
|
||||
opt.Add(find);
|
||||
return 0;
|
||||
} else
|
||||
{
|
||||
RegexFindComplexIR find = new(true, -1)
|
||||
{
|
||||
IsFullyOptimized = true
|
||||
};
|
||||
opt.Add(find);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#region merge find and try-find
|
||||
|
||||
private static int TyrMergeFindRestTwice(
|
||||
Span<RegexIR> window,
|
||||
List<RegexIR> opt
|
||||
)
|
||||
{
|
||||
if (window[0] is not RegexFindComplexIR { CommandCount: -1 } find_1 ||
|
||||
window[1] is not RegexFindComplexIR find_2) {
|
||||
return -1;
|
||||
}
|
||||
RegexFindComplexIR find_try = new(find_1.IsNearest && find_2.IsNearest, find_2.CommandCount)
|
||||
{
|
||||
IsFullyOptimized = true
|
||||
};
|
||||
opt.Add(find_try);
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int TyrMergeFindRestTry(
|
||||
Span<RegexIR> window,
|
||||
List<RegexIR> opt
|
||||
)
|
||||
{
|
||||
if (window[0] is not RegexFindComplexIR { CommandCount: -1 } _find ||
|
||||
window[1] is not RegexTryIR _try) {
|
||||
return -1;
|
||||
}
|
||||
RegexFindComplexIR find_try = new(_find.IsNearest, _try.CommandCount)
|
||||
{
|
||||
IsFullyOptimized = true
|
||||
};
|
||||
opt.Add(find_try);
|
||||
return find_try.CommandCount;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region expand once
|
||||
|
||||
private static void TryExpandComplex(
|
||||
Span<RegexIR> complex,
|
||||
List<RegexIR> opt,
|
||||
bool is_once
|
||||
)
|
||||
{
|
||||
if (!is_once) {
|
||||
opt.Add(new RegexTryIR(complex.Length));
|
||||
}
|
||||
|
||||
opt.AddRange(complex);
|
||||
}
|
||||
|
||||
private static int TryExpandOnce(
|
||||
Span<RegexIR> window,
|
||||
List<RegexIR> opt
|
||||
)
|
||||
{
|
||||
RegexIR ir = window[0];
|
||||
if (ir is IRegexCount { Min: 1, Max: 1 })
|
||||
{
|
||||
string pattern;
|
||||
switch (ir.CommandMask & RegexIRCommand.Count)
|
||||
{
|
||||
case RegexIRCommand.Count_Complex:
|
||||
RegexCountComplexIR complex = Unsafe.As<RegexCountComplexIR>(ir);
|
||||
return complex.CommandCount;
|
||||
case RegexIRCommand.Count_Seq:
|
||||
RegexCountSeqIR seq = Unsafe.As<RegexCountSeqIR>(ir);
|
||||
pattern = seq.Pattern;
|
||||
opt.Add(new RegexMatchSeqIR(pattern));
|
||||
return 0;
|
||||
case RegexIRCommand.Count_Set:
|
||||
RegexCountSetIR set = Unsafe.As<RegexCountSetIR>(ir);
|
||||
pattern = set.CharSet;
|
||||
opt.Add(new RegexMatchSetIR(pattern));
|
||||
return 0;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
} else if (ir is IRegexCount { Min: 0, Max: 1 })
|
||||
{
|
||||
string pattern;
|
||||
switch (ir.CommandMask & RegexIRCommand.Count)
|
||||
{
|
||||
case RegexIRCommand.Count_Complex:
|
||||
RegexCountComplexIR complex = Unsafe.As<RegexCountComplexIR>(ir);
|
||||
return complex.CommandCount;
|
||||
case RegexIRCommand.Count_Seq:
|
||||
RegexCountSeqIR seq = Unsafe.As<RegexCountSeqIR>(ir);
|
||||
pattern = seq.Pattern;
|
||||
opt.Add(new RegexTryIR(1));
|
||||
opt.Add(new RegexMatchSeqIR(pattern));
|
||||
return 0;
|
||||
case RegexIRCommand.Count_Set:
|
||||
opt.Add(new RegexTryIR(1));
|
||||
RegexCountSetIR set = Unsafe.As<RegexCountSetIR>(ir);
|
||||
pattern = set.CharSet;
|
||||
opt.Add(new RegexMatchSetIR(pattern));
|
||||
return 0;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
} else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region expand plus
|
||||
|
||||
private static void TryExpandComplexPlus(
|
||||
bool is_greedy,
|
||||
Span<RegexIR> complex,
|
||||
List<RegexIR> opt
|
||||
)
|
||||
{
|
||||
ScanAndOptimizeIR(complex, out List<RegexIR>? part_opt);
|
||||
opt.AddRange(part_opt);
|
||||
RegexCountComplexIR c_ir = new(0, -1, is_greedy, part_opt.Count)
|
||||
{
|
||||
IsFullyOptimized = true
|
||||
};
|
||||
opt.Add(c_ir);
|
||||
opt.AddRange(part_opt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="window">
|
||||
///
|
||||
/// </param>
|
||||
/// <param name="opt">
|
||||
///
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Negative if cannot opt, 0 if match successfully, positive if meets an IRegexComplex
|
||||
/// interface, need to call TryExpandComplexPlus
|
||||
/// </returns>
|
||||
private static int TryExpandPlus(
|
||||
Span<RegexIR> window,
|
||||
List<RegexIR> opt
|
||||
)
|
||||
{
|
||||
/* a+
|
||||
* convert to
|
||||
* aa*
|
||||
*/
|
||||
|
||||
RegexIR ir = window[0];
|
||||
if (ir is not IRegexCount { IsPlus: true } count) {
|
||||
return -1;
|
||||
}
|
||||
bool is_greedy = count.IsGreedy;
|
||||
string pattern;
|
||||
switch (ir.CommandMask & RegexIRCommand.Count)
|
||||
{
|
||||
case RegexIRCommand.Count_Complex:
|
||||
RegexCountComplexIR complex = Unsafe.As<RegexCountComplexIR>(ir);
|
||||
return complex.CommandCount;
|
||||
case RegexIRCommand.Count_Seq:
|
||||
RegexCountSeqIR seq = Unsafe.As<RegexCountSeqIR>(ir);
|
||||
pattern = seq.Pattern;
|
||||
opt.Add(new RegexMatchSeqIR(pattern));
|
||||
opt.Add(new RegexCountSeqIR(0, -1, is_greedy, pattern));
|
||||
return 0;
|
||||
case RegexIRCommand.Count_Set:
|
||||
RegexCountSeqIR set = Unsafe.As<RegexCountSeqIR>(ir);
|
||||
pattern = set.Pattern;
|
||||
opt.Add(new RegexMatchSeqIR(pattern));
|
||||
opt.Add(new RegexCountSeqIR(0, -1, is_greedy, pattern));
|
||||
return 0;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,531 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegexAnalyzer.PrefixEmit"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nitload.RegularAnalysis.Text
|
||||
{
|
||||
public static partial class RegexAnalyzer
|
||||
{
|
||||
|
||||
internal static List<RegexIR> EmitPrefix(
|
||||
List<RegexIR> irList,
|
||||
ref RegexInfo info
|
||||
)
|
||||
{
|
||||
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);
|
||||
int single_line_count = FixLineObservation(result);
|
||||
info.RegionCount = region_name - 1;
|
||||
info.SingleLineResultCount = single_line_count;
|
||||
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 =
|
||||
TryEmitComplexIR(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);
|
||||
MarkPopResult(previous, RegexIRPrefixMask.Pop_Success);
|
||||
}
|
||||
|
||||
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];
|
||||
MarkPopResult(previous, RegexIRPrefixMask.Pop_Success);
|
||||
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;
|
||||
}
|
||||
|
||||
int TryEmitComplexIR(
|
||||
ReadOnlySpan<RegexIR> origin,
|
||||
List<RegexIR> result,
|
||||
int currentRegion,
|
||||
int lineBase
|
||||
)
|
||||
{
|
||||
if (origin.Length == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (origin[0] is RegexCountComplexIR count)
|
||||
{
|
||||
int commandCount = count.CommandCount;
|
||||
if (origin.Length < commandCount + 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
List<RegexIR> sub_ir = new(commandCount);
|
||||
EmitPrefix(origin.Slice(1, commandCount), sub_ir, currentRegion,
|
||||
lineBase + result.Count + 1);
|
||||
|
||||
RegexCountComplexIR newCount = new(count.Min, count.Max, count.IsGreedy,
|
||||
sub_ir.Count)
|
||||
{
|
||||
IsFullyOptimized = count.IsFullyOptimized,
|
||||
Prefix =
|
||||
new RegexIRPrefix(
|
||||
count.Prefix.WorkingRegion == 0 ?
|
||||
currentRegion :
|
||||
count.Prefix.WorkingRegion,
|
||||
count.Prefix.Mask,
|
||||
count.Prefix.ObserveSource)
|
||||
};
|
||||
|
||||
result.Add(newCount);
|
||||
result.AddRange(sub_ir);
|
||||
return commandCount + 1;
|
||||
}
|
||||
|
||||
if (origin[0] is RegexFindComplexIR { CommandCount: > 0 } find)
|
||||
{
|
||||
int commandCount = find.CommandCount;
|
||||
if (origin.Length < commandCount + 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
List<RegexIR> sub_ir = new(commandCount);
|
||||
EmitPrefix(origin.Slice(1, commandCount), sub_ir, currentRegion,
|
||||
lineBase + result.Count + 1);
|
||||
|
||||
RegexFindComplexIR newFind = new(find.IsNearest, sub_ir.Count)
|
||||
{
|
||||
IsFullyOptimized = find.IsFullyOptimized,
|
||||
Prefix =
|
||||
new RegexIRPrefix(
|
||||
find.Prefix.WorkingRegion == 0 ?
|
||||
currentRegion :
|
||||
find.Prefix.WorkingRegion,
|
||||
find.Prefix.Mask,
|
||||
find.Prefix.ObserveSource)
|
||||
};
|
||||
|
||||
result.Add(newFind);
|
||||
result.AddRange(sub_ir);
|
||||
return commandCount + 1;
|
||||
}
|
||||
|
||||
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 int FixLineObservation(
|
||||
List<RegexIR> result
|
||||
)
|
||||
{
|
||||
// Observe_Line uses relative offsets while nested regions are emitted.
|
||||
// Positive offsets point to larger line numbers; this pass resolves them
|
||||
// into absolute IR line indexes after the final instruction order is known.
|
||||
HashSet<int> single_required = [];
|
||||
for (int i = 0; i < result.Count; i++)
|
||||
{
|
||||
RegexIRPrefix prefix = result[i].Prefix;
|
||||
if ((prefix.Mask & RegexIRPrefixMask.Observe_Line) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int observeSource = i + prefix.ObserveSource;
|
||||
RegexIRPrefix prefix_new = new RegexIRPrefix(
|
||||
prefix.WorkingRegion,
|
||||
prefix.Mask,
|
||||
observeSource);
|
||||
result[i].Prefix = prefix_new;
|
||||
_ = single_required.Add(prefix_new.ObserveSource);
|
||||
}
|
||||
return single_required.Count;
|
||||
}
|
||||
|
||||
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
|
||||
//RegexFindSetIR or
|
||||
RegexFindComplexIR;
|
||||
}
|
||||
|
||||
static void ApplyPrefix(
|
||||
RegexIR ir,
|
||||
int workingRegion,
|
||||
RegexIRPrefixMask mask,
|
||||
int observeSource
|
||||
)
|
||||
{
|
||||
ir.Prefix = new RegexIRPrefix(
|
||||
workingRegion,
|
||||
ir.Prefix.Mask | mask,
|
||||
observeSource);
|
||||
}
|
||||
|
||||
// Only annotate the pop result of an already emitted instruction.
|
||||
// The working region and observe source belong to the execution guard
|
||||
// and must stay untouched, otherwise a region id leaks into ObserveSource.
|
||||
static void MarkPopResult(
|
||||
RegexIR ir,
|
||||
RegexIRPrefixMask popMask
|
||||
)
|
||||
{
|
||||
ir.Prefix = new RegexIRPrefix(
|
||||
ir.Prefix.WorkingRegion,
|
||||
ir.Prefix.Mask | popMask,
|
||||
ir.Prefix.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.Pattern),
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegexIR.Abstraction"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nitload.RegularAnalysis.Text
|
||||
{
|
||||
internal interface IRegexComplex
|
||||
{
|
||||
|
||||
bool IsFullyOptimized { get; set; }
|
||||
|
||||
int CommandCount { get; set; }
|
||||
|
||||
}
|
||||
|
||||
internal interface IRegexSequence
|
||||
{
|
||||
|
||||
string Pattern { get; }
|
||||
|
||||
}
|
||||
|
||||
internal interface IRegexCount
|
||||
{
|
||||
|
||||
bool IsGreedy { get; }
|
||||
|
||||
bool IsStar { get; }
|
||||
|
||||
bool IsPlus { get; }
|
||||
|
||||
int Min { get; }
|
||||
|
||||
int Max { get; }
|
||||
|
||||
}
|
||||
|
||||
internal abstract class RegexIR
|
||||
{
|
||||
|
||||
protected RegexIR(RegexIRCommand command)
|
||||
{
|
||||
CommandMask = command;
|
||||
}
|
||||
|
||||
public bool SingleLineResultRequired { get; set; }
|
||||
|
||||
public int SingleLineResultIndex { get; set; }
|
||||
|
||||
public RegexIRPrefix Prefix { get; set; }
|
||||
|
||||
internal RegexIRCommand CommandMask { get; init; }
|
||||
|
||||
internal string AsCommand(int lineNum)
|
||||
{
|
||||
return $"[{lineNum:D3}] {Prefix}, {AsCommand()}";
|
||||
}
|
||||
|
||||
protected abstract string AsCommand();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegexIR.Prefix"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nitload.RegularAnalysis.Text
|
||||
{
|
||||
internal enum RegexIRPrefixMask
|
||||
{
|
||||
|
||||
Prev_Success = 1 << 0,
|
||||
Prev_Fail = 1 << 1,
|
||||
Always = 1 << 2,
|
||||
ExeCondition = Prev_Fail | Prev_Success | Always,
|
||||
Pop_Fail = 1 << 3,
|
||||
Pop_Success = 1 << 4,
|
||||
PopCondition = Pop_Success | Pop_Fail,
|
||||
Observe_Line = 1 << 5,
|
||||
Observe_Region = 1 << 6,
|
||||
ObserveMethod = Observe_Line | Observe_Region
|
||||
|
||||
}
|
||||
|
||||
internal record struct RegexIRPrefix(int WorkingRegion, RegexIRPrefixMask Mask, int ObserveSource)
|
||||
{
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{WorkingRegion:D4}, {DisplayMask(Mask , RegexIRPrefixMask.ExeCondition)}, {DisplayMask(Mask, RegexIRPrefixMask.PopCondition)}, {DisplayMask(Mask,RegexIRPrefixMask.ObserveMethod)}, {ObserveSource:D4}";
|
||||
}
|
||||
|
||||
private static string DisplayMask(RegexIRPrefixMask mask, RegexIRPrefixMask range)
|
||||
{
|
||||
return range switch
|
||||
{
|
||||
RegexIRPrefixMask.ExeCondition => ((int)(mask & RegexIRPrefixMask.ExeCondition) >> 0) switch
|
||||
{
|
||||
0 => "DEF ",
|
||||
(1 << 0) => "Sccs",
|
||||
(1 << 1) => "Fail",
|
||||
(1 << 2) => "Alws",
|
||||
_ => "????"
|
||||
},
|
||||
RegexIRPrefixMask.PopCondition => ((int)(mask & RegexIRPrefixMask.PopCondition) >> 3) switch
|
||||
{
|
||||
0 => "DEF ",
|
||||
(1 << 0) => "Fail",
|
||||
(1 << 1) => "Sccs",
|
||||
_ => "????"
|
||||
},
|
||||
RegexIRPrefixMask.ObserveMethod => ((int)(mask & RegexIRPrefixMask.ObserveMethod) >> 5) switch
|
||||
{
|
||||
0 => "NONE",
|
||||
(1 << 0) => "LINE",
|
||||
(1 << 1) => "REGI",
|
||||
_ => "????"
|
||||
},
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(range), range, null)
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegexIR"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nitload.RegularAnalysis.Text
|
||||
{
|
||||
internal enum RegexIRCommand
|
||||
{
|
||||
|
||||
Match_Seq = 1 << 0,
|
||||
Match_Set = 1 << 1,
|
||||
Condition = 1 << 2,
|
||||
Try = 1 << 3,
|
||||
Count_Complex = 1 << 4,
|
||||
Count_Seq = 1 << 5,
|
||||
Count_Set = 1 << 6,
|
||||
Pop = 1 << 7,
|
||||
Pack = 1 << 8,
|
||||
Find_Seq = 1 << 9,
|
||||
Find_Set = 1 << 10,
|
||||
Find_Complex = 1 << 11,
|
||||
|
||||
Match = Match_Seq | Match_Set,
|
||||
Count = Count_Seq | Count_Set | Count_Complex,
|
||||
Find = Find_Seq | Find_Set | Find_Complex,
|
||||
|
||||
}
|
||||
|
||||
internal sealed class RegexFindComplexIR(
|
||||
bool nearest,
|
||||
int count
|
||||
) : RegexIR(RegexIRCommand.Find_Complex), IRegexComplex
|
||||
{
|
||||
|
||||
public bool IsFullyOptimized { get; set; }
|
||||
|
||||
public bool IsNearest { get; } = nearest;
|
||||
|
||||
public int CommandCount { get; set; } = count;
|
||||
|
||||
protected override string AsCommand()
|
||||
{
|
||||
return $"FIND_COMPLEX {(IsNearest ? "NEAREST" : "FURTHEST")} COUNT: {CommandCount}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal sealed class RegexFindSeqIR(
|
||||
string pattern,
|
||||
bool isFirst
|
||||
) : RegexIR(RegexIRCommand.Find_Seq), IRegexSequence
|
||||
{
|
||||
|
||||
public bool IsFirst { get; } = isFirst;
|
||||
|
||||
public string Pattern { get; } = pattern;
|
||||
|
||||
protected override string AsCommand()
|
||||
{
|
||||
return $"FIND_SEQ \"{(string.IsNullOrEmpty(Pattern) ? "<EPSILON>" : Pattern)}\"";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal interface IRegexMatch { }
|
||||
|
||||
/// <summary>
|
||||
/// Searches for a certain string pattern or charset from current position.
|
||||
/// </summary>
|
||||
internal sealed class RegexMatchSeqIR(
|
||||
string pattern
|
||||
) : RegexIR(RegexIRCommand.Match_Seq), IRegexSequence, IRegexMatch
|
||||
{
|
||||
|
||||
public string Pattern { get; } = pattern;
|
||||
|
||||
protected override string AsCommand()
|
||||
{
|
||||
return $"MATCH_SEQ \"{(string.IsNullOrEmpty(Pattern) ? "<EPSILON>" : Pattern)}\"";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal sealed class RegexMatchSetIR(
|
||||
string charset
|
||||
) : RegexIR(RegexIRCommand.Match_Set), IRegexMatch
|
||||
{
|
||||
|
||||
public string Charset { get; } = charset;
|
||||
|
||||
protected override string AsCommand()
|
||||
{
|
||||
return $"MATCH_SET \"{Charset}\"";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal sealed class RegexConditionIR(
|
||||
int success,
|
||||
int fail
|
||||
) : RegexIR(RegexIRCommand.Condition)
|
||||
{
|
||||
|
||||
public int SuccessCase { get; } = success;
|
||||
|
||||
public int FailCase { get; } = fail;
|
||||
|
||||
protected override string AsCommand()
|
||||
{
|
||||
return $"CONDITION SUCCESS: {SuccessCase} FAIL: {FailCase}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal sealed class RegexPackIR(
|
||||
bool condition,
|
||||
string name = ""
|
||||
) : RegexIR(RegexIRCommand.Pack)
|
||||
{
|
||||
|
||||
public bool Condition { get; } = condition;
|
||||
|
||||
public string GroupName { get; } = name;
|
||||
|
||||
protected override string AsCommand()
|
||||
{
|
||||
return $"PACK {(Condition ? "SUCCESS" : "FAIL")} GROUP: \"{GroupName}\"";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal sealed class RegexCountComplexIR(
|
||||
int min,
|
||||
int max,
|
||||
bool isGreedy,
|
||||
int operation
|
||||
) : RegexIR(RegexIRCommand.Count_Complex), IRegexCount, IRegexComplex
|
||||
{
|
||||
|
||||
public bool IsGreedy { get; } = isGreedy;
|
||||
|
||||
public bool IsStar => Min == 0 && Max < 0;
|
||||
|
||||
public bool IsPlus => Min == 1 && Max < 0;
|
||||
|
||||
public bool IsFullyOptimized { get; set; }
|
||||
|
||||
public int Min { get; } = min;
|
||||
|
||||
public int Max { get; } = max;
|
||||
|
||||
public int CommandCount { get; set; } = operation;
|
||||
|
||||
protected override string AsCommand()
|
||||
{
|
||||
return $"COUNT {(IsGreedy ? "GREEDY" : "NON_GREEDY")} MIN: {Min} MAX: {Max} OPERATION: {CommandCount}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal sealed class RegexCountSeqIR(
|
||||
int min,
|
||||
int max,
|
||||
bool isGreedy,
|
||||
string seq
|
||||
) : RegexIR(RegexIRCommand.Count_Seq), IRegexCount, IRegexSequence
|
||||
{
|
||||
|
||||
public bool IsGreedy { get; } = isGreedy;
|
||||
|
||||
public bool IsStar => Min == 0 && Max < 0;
|
||||
|
||||
public bool IsPlus => Min == 1 && Max < 0;
|
||||
|
||||
public int Min { get; } = min;
|
||||
|
||||
public int Max { get; } = max;
|
||||
|
||||
public string Pattern { get; } = seq.Length == 2 && seq[0] == '\\' ? seq[1..2] : seq;
|
||||
|
||||
protected override string AsCommand()
|
||||
{
|
||||
return $"COUNT_SEQ {(IsGreedy ? "GREEDY" : "NON_GREEDY")} MIN: {Min} MAX: {Max} SEQ: \"{Pattern}\"";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal sealed class RegexCountSetIR(
|
||||
int min,
|
||||
int max,
|
||||
bool isGreedy,
|
||||
string set
|
||||
) : RegexIR(RegexIRCommand.Count_Set), IRegexCount
|
||||
{
|
||||
|
||||
public bool IsGreedy { get; } = isGreedy;
|
||||
|
||||
public bool IsDotStar => CharSet == "." && Min == 0 && Max < 0;
|
||||
|
||||
public bool IsStar => Min == 0 && Max < 0;
|
||||
|
||||
public bool IsPlus => Min == 1 && Max < 0;
|
||||
|
||||
public int Min { get; } = min;
|
||||
|
||||
public int Max { get; } = max;
|
||||
|
||||
public string CharSet { get; } = set;
|
||||
|
||||
protected override string AsCommand()
|
||||
{
|
||||
return $"COUNT_SET {(IsGreedy ? "GREEDY" : "NON_GREEDY")} MIN: {Min} MAX: {Max} SET: \"{CharSet}\"";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal sealed class RegexTryIR(
|
||||
int count
|
||||
) : RegexIR(RegexIRCommand.Try), IRegexComplex, IRegexMatch
|
||||
{
|
||||
|
||||
public bool IsFullyOptimized { get; set; }
|
||||
|
||||
public int CommandCount { get; set; } = count;
|
||||
|
||||
protected override string AsCommand()
|
||||
{
|
||||
return $"TRY {CommandCount}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal sealed class RegexFindSetIR(
|
||||
string charset,
|
||||
bool isFirst
|
||||
) : RegexIR(RegexIRCommand.Find_Set)
|
||||
{
|
||||
|
||||
public bool IsFirst { get; } = isFirst;
|
||||
|
||||
public string Charset { get; } = charset;
|
||||
|
||||
protected override string AsCommand()
|
||||
{
|
||||
return $"FIND_SET \"{Charset}\"";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal sealed class RegexPopIR(
|
||||
bool isSuccess
|
||||
) : RegexIR(RegexIRCommand.Pop)
|
||||
{
|
||||
|
||||
public bool IsSuccess { get; } = isSuccess;
|
||||
|
||||
protected override string AsCommand()
|
||||
{
|
||||
return $"POP {(IsSuccess ? "SUCCESS" : "FAIL")}";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegexIRGenerator"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Nitload.RegularAnalysis.Abstraction;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nitload.RegularAnalysis.Text
|
||||
{
|
||||
internal sealed class RegexIRGenerator
|
||||
{
|
||||
|
||||
private readonly SequenceEmitter<RegexIR> _stream = new();
|
||||
|
||||
public int Count => _stream.Count;
|
||||
|
||||
public RegexIRGenerator EmitCondition(
|
||||
int success,
|
||||
int fail
|
||||
)
|
||||
{
|
||||
return Emit(new RegexConditionIR(success, fail));
|
||||
}
|
||||
|
||||
public RegexIRGenerator EmitCountComplex(
|
||||
int min,
|
||||
int max,
|
||||
bool isGreedy,
|
||||
int operation
|
||||
)
|
||||
{
|
||||
return Emit(new RegexCountComplexIR(min, max, isGreedy, operation));
|
||||
}
|
||||
|
||||
public RegexIRGenerator EmitCountSeq(
|
||||
int min,
|
||||
int max,
|
||||
bool isGreedy,
|
||||
string seq
|
||||
)
|
||||
{
|
||||
return Emit(new RegexCountSeqIR(min, max, isGreedy, seq));
|
||||
}
|
||||
|
||||
public RegexIRGenerator EmitCountSet(
|
||||
int min,
|
||||
int max,
|
||||
bool isGreedy,
|
||||
string set
|
||||
)
|
||||
{
|
||||
return Emit(new RegexCountSetIR(min, max, isGreedy, set));
|
||||
}
|
||||
|
||||
public RegexIRGenerator EmitIRStream(
|
||||
RegexIRGenerator generator
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(generator);
|
||||
|
||||
_ = _stream.Append(generator._stream);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RegexIRGenerator EmitMatchSeq(
|
||||
string unit
|
||||
)
|
||||
{
|
||||
return Emit(new RegexMatchSeqIR(unit));
|
||||
}
|
||||
|
||||
public RegexIRGenerator EmitMatchSet(
|
||||
string unit
|
||||
)
|
||||
{
|
||||
return Emit(new RegexMatchSetIR(unit));
|
||||
}
|
||||
|
||||
public RegexIRGenerator EmitPack(
|
||||
bool condition,
|
||||
string name = ""
|
||||
)
|
||||
{
|
||||
return Emit(new RegexPackIR(condition, name));
|
||||
}
|
||||
|
||||
public RegexIRGenerator EmitPop(
|
||||
bool isSuccess
|
||||
)
|
||||
{
|
||||
return Emit(new RegexPopIR(isSuccess));
|
||||
}
|
||||
|
||||
public RegexIRGenerator EmitTry(
|
||||
int count
|
||||
)
|
||||
{
|
||||
return Emit(new RegexTryIR(count));
|
||||
}
|
||||
|
||||
public SequenceEmitter<RegexIR>.Enumerator GetEnumerator()
|
||||
{
|
||||
return _stream.GetEnumerator();
|
||||
}
|
||||
|
||||
public List<RegexIR> ToList()
|
||||
{
|
||||
return _stream.ToList();
|
||||
}
|
||||
|
||||
private RegexIRGenerator Emit(
|
||||
RegexIR ir
|
||||
)
|
||||
{
|
||||
_ = _stream.Append(ir);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="TextRegexSourceGen.GenClass.MultiIR"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Nitload.RegularAnalysis.Abstraction;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization.Formatters;
|
||||
using System.Threading;
|
||||
|
||||
namespace Nitload.RegularAnalysis.Text
|
||||
{
|
||||
internal partial class TextRegexSourceGen
|
||||
{
|
||||
/*
|
||||
* =======================================================================================
|
||||
* L1, several regions plus a tail
|
||||
* =======================================================================================
|
||||
*
|
||||
* Highest consumption in the dispatch chain, so these are offered the window first. Each
|
||||
* shape here was already recognised once during prefix emission, so a generator should
|
||||
* mirror the matching RegexAnalyzer.PrefixEmit recogniser rather than re-derive the shape.
|
||||
*
|
||||
* L1.2 ALT chain with a shared suffix
|
||||
* window L1.1 followed by linear instructions in the parent region
|
||||
* sample (ab|[cd]|.)x
|
||||
* nature flattening, may return -1
|
||||
*
|
||||
* L1.3 group tail
|
||||
* window region, POP FAIL with Prev_Fail, PACK SUCCESS with Prev_Success
|
||||
* mirrors TryEmitTryGroupTail
|
||||
* shape region block, capture written on the success branch
|
||||
* sample (?<name>abc)
|
||||
* nature flattening, may return -1
|
||||
*
|
||||
* L1.4 group tail wrapping an ALT chain
|
||||
* sample (?<a>abcd|a(bc)c)
|
||||
* nature flattening, may return -1
|
||||
*
|
||||
* L1.5 COUNT_COMPLEX plus body
|
||||
* window RegexCountComplexIR, then CommandCount entries
|
||||
* mirrors TryEmitComplexIR
|
||||
* shape loop wrapping the generated body
|
||||
* sample (ab){2,3}
|
||||
* nature REQUIRED, no fallback exists
|
||||
*
|
||||
* L1.6 COUNT_COMPLEX whose body contains an ALT chain, plus a linear suffix
|
||||
* sample (123(4|5)67){3,4}114514
|
||||
* nature REQUIRED, no fallback exists
|
||||
*
|
||||
* L1.7 FIND_COMPLEX with CommandCount >= 0
|
||||
* window RegexFindComplexIR, then CommandCount entries
|
||||
* shape candidate retry loop wrapping the body
|
||||
* sample .*(ab|cd)
|
||||
* nature REQUIRED, no fallback exists
|
||||
*
|
||||
* L1.8 FIND_COMPLEX with CommandCount == -1
|
||||
* window RegexFindComplexIR, then the rest of the region
|
||||
* shape candidate retry loop wrapping the rest of the region
|
||||
* sample (?<name>.*)234
|
||||
* nature REQUIRED, no fallback exists
|
||||
*
|
||||
* L1.9 FIND_SEQ or FIND_SET
|
||||
* window the instruction, then the rest of the region
|
||||
* shape candidate retry loop wrapping the rest of the region
|
||||
* sample .*abc
|
||||
* nature REQUIRED, no fallback exists
|
||||
*
|
||||
* L1.5 to L1.9 are required because none of their bodies is a region of its own. L2
|
||||
* cannot pick them up, so there is no degradation path if these are missing.
|
||||
*
|
||||
* CommandCount must be read from the prefixed IR. Prefix emission rebuilds these counts,
|
||||
* so the value carried by the pre-prefix IR no longer applies.
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* Backtracking semantics of the FIND family
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* A FIND does not yield one position, it yields a priority ordered sequence of candidates.
|
||||
* FURTHEST starts at the last one and steps backwards on failure, NEAREST starts at the
|
||||
* first and steps forwards. The body is the verifier for a candidate: when it fails, the
|
||||
* next candidate has to be tried, and only an exhausted candidate list is a real failure.
|
||||
* A single LastIndexOf is wrong, because .*abcX has to fall back to the second to last abc
|
||||
* when the last one is not followed by an X.
|
||||
*
|
||||
* CommandCount == -1 means the body is the rest of the region. TryReplaceDotStar emits
|
||||
* FindComplex(-1) for a bare .*, then TyrMergeFindRestTwice and TyrMergeFindRestTry try to
|
||||
* collapse it into a definite length. Whatever survives stays at -1. FIND_SEQ and FIND_SET
|
||||
* carry no CommandCount at all and behave like -1. Only a FIND that happens to be the last
|
||||
* instruction of its region degrades into a single search, consuming one entry.
|
||||
*
|
||||
* Target shape:
|
||||
*
|
||||
* int __bound = text.Length;
|
||||
* __ok = false;
|
||||
* while (true)
|
||||
* {
|
||||
* int __at = text[__cur..__bound].LastIndexOf("abc");
|
||||
* if (__at < 0) { __ok = false; break; } // candidates exhausted, only failure
|
||||
* int __save = __cur;
|
||||
* __cur = __cur + __at + 3;
|
||||
*
|
||||
* // body, or the rest of the region, expands here
|
||||
*
|
||||
* if (__ok) { break; } // verified
|
||||
* __cur = __save;
|
||||
* __bound = __save + __at; // step to the previous candidate
|
||||
* }
|
||||
*
|
||||
* POP conflicts with the retry loop. A POP lowers to break, and the retry while is nearer
|
||||
* than the region do block, so the break would leave the wrong scope. The fix without goto
|
||||
* is a region completion flag:
|
||||
*
|
||||
* do // region N
|
||||
* {
|
||||
* bool __rN_done = false;
|
||||
* while (!__rN_done) // find retry
|
||||
* {
|
||||
* // candidate search
|
||||
* do // isolation layer, POP breaks out here
|
||||
* {
|
||||
* // body, POP sets __rN_ok, sets __rN_done, breaks
|
||||
* } while (false);
|
||||
* if (__rN_done || __ok) { break; }
|
||||
* // step to the next candidate
|
||||
* }
|
||||
* } while (false);
|
||||
*
|
||||
* Two further hazards:
|
||||
* - nested FIND produces nested retry loops and the cost multiplies, which is the
|
||||
* catastrophic backtracking shape, worth a guard or at least a marker
|
||||
* - __ok is overwritten across retry iterations, so an Observe_Line prefix pointing into
|
||||
* a retry body reads the value of the last iteration, a case the current prefix model
|
||||
* does not cover
|
||||
*
|
||||
* Open questions to settle while implementing:
|
||||
* - how the cursor is restored between ALT cases
|
||||
* - which successful outcome a PACK uses as its capture span
|
||||
* - whether a region body ever deserves its own generated method instead of inlining
|
||||
*
|
||||
* =======================================================================================
|
||||
* L2, one region
|
||||
* =======================================================================================
|
||||
*
|
||||
* Consumes CommandCount + 1, the whole block a TRY lowers to. Together with L4 and L5 this
|
||||
* is the correctness core: once these three exist, every expression can be generated, just
|
||||
* with deeper nesting than L1 and L3 would produce.
|
||||
*
|
||||
* L2.1 RegexTryIR plus its body sample (abc)
|
||||
* L2.2 the same, body containing a nested region sample (a(bc))
|
||||
* L2.3 a single instruction region folded by prefix emission sample (a)
|
||||
*
|
||||
* Target shape, names owned by SourceGenContext:
|
||||
*
|
||||
* __rN_begin = __cur;
|
||||
* __rN_ok = false;
|
||||
* do
|
||||
* {
|
||||
* // body, a POP breaks out here
|
||||
* __rN_ok = __ok;
|
||||
* } while (false);
|
||||
* if (!__rN_ok) { __cur = __rN_begin; }
|
||||
* __ok = __rN_ok;
|
||||
*
|
||||
* Points the implementation has to settle:
|
||||
* - the failure path restores the cursor to the region entry
|
||||
* - break only leaves the nearest enclosing loop, and a FIND retry while counts as one,
|
||||
* so a region containing a FIND needs the __rN_done flag described above instead of a
|
||||
* plain break
|
||||
* - a nested region must not rely on break to exit an outer one
|
||||
* - the whole match body is itself region zero, which is what makes a top level POP
|
||||
* legal
|
||||
*
|
||||
* =======================================================================================
|
||||
* L3, fixed multi instruction windows
|
||||
* =======================================================================================
|
||||
*
|
||||
* Consumes two to eight entries. Pure code quality work, never required for correctness.
|
||||
* Every one of these must return -1 cleanly when the window does not match, so the entry
|
||||
* falls through to L4.
|
||||
*
|
||||
* L3.1 FIND_COMPLEX FURTHEST COUNT:-1, PACK, MATCH_SEQ 3 (?<name>.*)234
|
||||
* L3.2 the same with a single element anchor 3 (?<name>.*)a
|
||||
* L3.3 FIND_SEQ, MATCH_SEQ, merged into one search 2 .*abc
|
||||
* L3.4 MATCH_SEQ, COUNT_SEQ, the expanded a+ shape 2 a{3,5}, a+
|
||||
* L3.5 a run of MATCH_SEQ and MATCH_SET, merged into one condition 2 to 8 a[abc]d
|
||||
* L3.6 trailing COUNT_SET over a dot, fast success 1 to 2 abc.*
|
||||
*
|
||||
* Two hazards to check for each of these:
|
||||
* - merging must not break the line indices an Observe_Line prefix refers to
|
||||
* - a merged window still has to leave __ok describing the whole window
|
||||
*/
|
||||
|
||||
#region code
|
||||
|
||||
private static int TryGenerateTryBlock(
|
||||
int outer_level,
|
||||
ReadOnlySpan<RegexIR> ir_span,
|
||||
SourceGenContext sc,
|
||||
in SequenceEmitter<string> code,
|
||||
in List<SequenceEmitter<string>> local_func
|
||||
)
|
||||
{
|
||||
if (ir_span[0]is not RegexTryIR _try) {
|
||||
return -1;
|
||||
}
|
||||
ReadOnlySpan<RegexIR> block = ir_span[1..(1 + _try.CommandCount)];
|
||||
|
||||
// TODO generate here
|
||||
return _try.CommandCount;
|
||||
}
|
||||
|
||||
private static int TryGenerateNamedCountAny(
|
||||
int outer_level,
|
||||
ReadOnlySpan<RegexIR> ir_span,
|
||||
SourceGenContext sc,
|
||||
in SequenceEmitter<string> code,
|
||||
in List<SequenceEmitter<string>> local_func
|
||||
)
|
||||
{
|
||||
/*
|
||||
* (?<name>.*)234
|
||||
* [010] 0003, DEF , DEF , NONE, 0000, FIND_COMPLEX FURTHEST COUNT: -1
|
||||
* [011] 0003, Sccs, DEF , LINE, 0010, PACK SUCCESS GROUP: "name"
|
||||
* [012] 0000, DEF , DEF , NONE, 0000, MATCH_SEQ "234"
|
||||
*/
|
||||
if (ir_span[0] is not RegexFindComplexIR { CommandCount: -1 } ||
|
||||
ir_span[1] is not RegexPackIR pack)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
string group_name = pack.GroupName;
|
||||
|
||||
int i = 0;
|
||||
for (; i < ir_span.Length; i++) { }
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int TryGenerateAltChain(
|
||||
int outer_level,
|
||||
ReadOnlySpan<RegexIR> ir_span,
|
||||
SourceGenContext sc,
|
||||
in SequenceEmitter<string> code,
|
||||
in List<SequenceEmitter<string>> local_func
|
||||
)
|
||||
{
|
||||
/*
|
||||
* L1.1 ALT chain
|
||||
* window region, then further regions carrying Prev_Fail
|
||||
* mirrors TryEmitAltChain
|
||||
* shape if / else if / else
|
||||
* sample a|b, ab|cd|ef, a|bc|def
|
||||
* nature flattening, may return -1
|
||||
*/
|
||||
|
||||
/*
|
||||
* [004] 0001, DEF , DEF , NONE, 0000, MATCH_SEQ "abc"
|
||||
* [005] 0002, DEF , Sccs, NONE, 0000, MATCH_SEQ "d" <- an alt chain begins here
|
||||
* [006] 0002, Fail, Sccs, LINE, 0005, MATCH_SEQ "c" <- an alt chain ends here
|
||||
* [007] 0001, Fail, DEF , LINE, 0006, POP FAIL
|
||||
*/
|
||||
|
||||
const RegexIRPrefixMask simple_alt = RegexIRPrefixMask.Prev_Fail | RegexIRPrefixMask.Pop_Success;
|
||||
|
||||
int cur_level = ir_span[0].Prefix.WorkingRegion;
|
||||
if (cur_level == outer_level) {
|
||||
return -1;
|
||||
}
|
||||
int i = 1;
|
||||
RegexIR ir;
|
||||
for (; i < ir_span.Length; i++)
|
||||
{
|
||||
ir = ir_span[i];
|
||||
if ((ir.Prefix.Mask & simple_alt) == simple_alt)
|
||||
{
|
||||
if (TryGenerateSingleIR(outer_level, [ir], sc, code, local_func) > 0)
|
||||
{
|
||||
// TODO add additional code for the alt chain
|
||||
}
|
||||
|
||||
// simple match
|
||||
} else if (ir is RegexTryIR _try && (ir.Prefix.Mask & simple_alt) == simple_alt)
|
||||
{
|
||||
// match a try region
|
||||
ReadOnlySpan<RegexIR> body_span = ir_span.Slice(i + 1, i + _try.CommandCount);
|
||||
i += _try.CommandCount;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="TextRegexSourceGen.GenClass.Prefix"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace Nitload.RegularAnalysis.Text
|
||||
{
|
||||
internal partial class TextRegexSourceGen
|
||||
{
|
||||
|
||||
/*
|
||||
* =======================================================================================
|
||||
* L5, prefix decoration
|
||||
* =======================================================================================
|
||||
*
|
||||
* A prefix consumes no IR of its own. It wraps whatever an L1 to L4 body generator
|
||||
* produced:
|
||||
*
|
||||
* [prefix open ] if (<observed result>) {
|
||||
* [ body ] ... sets __ok, advances __cur
|
||||
* [ line slot ] __lK_ok = __ok;
|
||||
* [ pop ] if (__ok) { __rN_ok = true; break; }
|
||||
* [prefix close] }
|
||||
*
|
||||
* Keeping this outside the body generators is what lets L2 and L4 stay small. They only
|
||||
* describe what an instruction does, never when it runs or who reads the outcome. Without
|
||||
* this layer the other layers cannot be assembled into correct control flow at all, so L5
|
||||
* is part of the correctness core rather than an optimisation.
|
||||
*
|
||||
* Checklist, RegexIRPrefixMask on the left:
|
||||
*
|
||||
* L5.1 ExeCondition none run unconditionally
|
||||
* L5.2 ExeCondition Prev_Success wrap in if (observed)
|
||||
* L5.3 ExeCondition Prev_Fail wrap in if (!observed)
|
||||
* L5.4 ExeCondition Always run unconditionally, deliberately detached from
|
||||
* the surrounding condition chain
|
||||
* L5.5 ObserveMethod none read the result of the preceding instruction
|
||||
* L5.6 ObserveMethod Observe_Line read the slot named by ObserveSource
|
||||
* L5.7 ObserveMethod Observe_Region read the slot named by ObserveSource
|
||||
* L5.8 PopCondition Pop_Success write the region result true, then leave it
|
||||
* L5.9 PopCondition Pop_Fail write the region result false, then leave it
|
||||
* L5.10 WorkingRegion selects which region scope the code lands in
|
||||
*
|
||||
* Rules the implementation has to hold:
|
||||
*
|
||||
* - Observe_Line and Observe_Region must read the slot ObserveSource names. Neither may
|
||||
* degrade into reading the previous instruction, which is what an absent observe
|
||||
* method means and nothing else.
|
||||
* - Line slots are allocated only for lines some prefix actually observes. That demand
|
||||
* has to be collected in a pass before body generation, because an observer can be
|
||||
* emitted before the line it reads, for instance when a nested region observes a line
|
||||
* of its parent.
|
||||
* - A pop lowers to break, which leaves the nearest enclosing do while(false) block.
|
||||
* That block is the region the instruction works in, because every region is emitted
|
||||
* as one and the whole match body is wrapped as region zero.
|
||||
* - Open and close must be paired. A body generator that declines the window must be
|
||||
* detected before the opening brace is emitted, so bodies are generated into a
|
||||
* detached emitter first.
|
||||
*
|
||||
* The open call should report how many braces it opened, so the close call knows how many
|
||||
* to emit rather than recomputing the condition.
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="TextRegexSourceGen.GenClass.SingleIR"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Nitload.RegularAnalysis.Abstraction;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Nitload.RegularAnalysis.Text
|
||||
{
|
||||
internal partial class TextRegexSourceGen
|
||||
{
|
||||
/*
|
||||
* =======================================================================================
|
||||
* L4, one instruction
|
||||
* =======================================================================================
|
||||
*
|
||||
* The fallback of the dispatch chain. Every RegexIRCommand needs a generator here, because
|
||||
* this is the only layer guaranteed to be offered every window. L1 and L3 may decline, L4
|
||||
* may not.
|
||||
*
|
||||
* Each generator consumes exactly one entry and describes only what the instruction does.
|
||||
* When it runs, which result it reads and whether it leaves the region early are all added
|
||||
* by L5, so nothing here should inspect RegexIR.Prefix beyond WorkingRegion.
|
||||
*
|
||||
* Contract for a generated body, names owned by SourceGenContext:
|
||||
*
|
||||
* - assign the outcome to __ok
|
||||
* - advance __cur only on success
|
||||
* - never index __cur past the end of the input
|
||||
* - declare scratch variables inside a block of their own, so a repeated shape does not
|
||||
* collide with itself
|
||||
*
|
||||
* A body must be generated into a detached emitter and only spliced in once it is known to
|
||||
* be claimed. Emitting a prefix for an instruction that turns out to have no generator
|
||||
* would open a brace nobody closes.
|
||||
*
|
||||
* Checklist:
|
||||
*
|
||||
* L4.1 MATCH_SEQ abc, \., a\*b
|
||||
* L4.2 MATCH_SET, discrete [abc], [123abc], [.]
|
||||
* L4.3 MATCH_SET, range [1-9]
|
||||
* L4.4 MATCH_SET, escape class \w
|
||||
* L4.5 MATCH_SET, any element .
|
||||
* L4.6 COUNT_SEQ a*, a?
|
||||
* L4.7 COUNT_SET [abc]*, \w+, .{11,13}, [.]*
|
||||
* L4.10 POP ALT tail
|
||||
* L4.11 PACK (abc), ordinal group
|
||||
*
|
||||
* L4.8 and L4.9 were removed. FIND_SEQ and FIND_SET have no single instruction form,
|
||||
* because they have to drive a candidate retry loop over the rest of the region. See L1.9
|
||||
* in TextRegexSourceGen.GenClass.MultiIR.cs. Only a FIND that happens to be the last
|
||||
* instruction of its region degrades into a single search, and that is a branch of L1.9
|
||||
* rather than a generator of its own. The numbers are left vacant so existing references
|
||||
* do not shift.
|
||||
*
|
||||
* Semantics worth pinning down before writing these:
|
||||
*
|
||||
* - [.] and . are different sets. The former is a literal dot, the latter is any
|
||||
* element, and [.]* must not be folded into the dot star optimisation.
|
||||
* - \w has to keep full .NET semantics. TextRegex.IsCharWord is inherited by the
|
||||
* generated class and already carries them, so the generated code can call it
|
||||
* directly instead of approximating with an ASCII test.
|
||||
* - Repetition in this layer does not backtrack. A lazy repetition therefore consumes
|
||||
* exactly its lower bound and leaves the rest to the following anchor. Backtracking
|
||||
* does exist in this backend, but only inside the FIND retry loop at L1.7 to L1.9,
|
||||
* never here.
|
||||
* - An epsilon sequence and an empty repetition unit both reach these generators.
|
||||
* Decide whether they are vacuous successes or an optimizer bug worth reporting.
|
||||
* - PACK decides nothing, so it should leave __ok untouched. Whether it runs at all is
|
||||
* already expressed by its execution condition.
|
||||
*/
|
||||
|
||||
#region code
|
||||
|
||||
private static int TryGenerateSingleIR(
|
||||
int outer_level,
|
||||
ReadOnlySpan<RegexIR> ir_span,
|
||||
SourceGenContext sc,
|
||||
in SequenceEmitter<string> code,
|
||||
in List<SequenceEmitter<string>> local_func
|
||||
)
|
||||
{
|
||||
return TryGenerateCountSeq(outer_level, ir_span, sc, code,
|
||||
local_func) > 0 ||
|
||||
TryGenerateCountSet(outer_level, ir_span, sc, code,
|
||||
local_func) > 0 ||
|
||||
TryGenerateMatchSet(outer_level, ir_span, sc, code,
|
||||
local_func) > 0 ||
|
||||
TryGenerateMatchSeq(outer_level, ir_span, sc, code, local_func) > 0 ?
|
||||
1 :
|
||||
-1;
|
||||
}
|
||||
|
||||
private static int TryGeneratePack(
|
||||
int outer_level,
|
||||
ReadOnlySpan<RegexIR> ir_span,
|
||||
SourceGenContext sc,
|
||||
in SequenceEmitter<string> code,
|
||||
in List<SequenceEmitter<string>> local_func
|
||||
)
|
||||
{
|
||||
if (ir_span[0] is not RegexPackIR pack) {
|
||||
return -1;
|
||||
}
|
||||
string group_name = pack.GroupName;
|
||||
string begin = sc.GetVarGroupBegin(group_name);
|
||||
string end = sc.GetVarGroupEnd(group_name);
|
||||
_ = code.AppendLine(begin).AppendLine(end);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int TryGenerateCountSet(
|
||||
int outer_level,
|
||||
ReadOnlySpan<RegexIR> ir_span,
|
||||
SourceGenContext sc,
|
||||
in SequenceEmitter<string> code,
|
||||
in List<SequenceEmitter<string>> local_func
|
||||
)
|
||||
{
|
||||
if (ir_span[0] is not RegexCountSetIR count_set) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool is_greedy = count_set.IsGreedy;
|
||||
int min = count_set.Min;
|
||||
int max = count_set.Max;
|
||||
int leave = is_greedy ? max : min;
|
||||
string counter = sc.RequireNewCounter();
|
||||
string set_source = count_set.CharSet;
|
||||
RegexCharSet[] set_arr = FigureCharSetType(set_source, out bool accept);
|
||||
|
||||
_ = code.AppendLine($@"for(; {counter}<{leave}; {counter}++)")
|
||||
.AppendLine("{")
|
||||
.AppendLine(accept ? @$"if(" : @$"if(!(");
|
||||
string _case_code;
|
||||
RegexCharSet set;
|
||||
for (int i = 1; i < set_arr.Length - 1; i++)
|
||||
{
|
||||
set = set_arr[i];
|
||||
_case_code = GenLine(set, sc, "||");
|
||||
_ = code.AppendLine(_case_code);
|
||||
}
|
||||
set = set_arr[^1];
|
||||
_case_code = GenLine(set, sc, ")");
|
||||
_ = code.AppendLine(_case_code, //
|
||||
"{", // in case match
|
||||
"}", //
|
||||
"else", //
|
||||
"{", //
|
||||
"break;", // in case miss match
|
||||
"}", //
|
||||
"}", // end of for
|
||||
@$"if({counter}>{min})", // match here
|
||||
"{", // in case count reaches min count request
|
||||
"}", //
|
||||
"else", //
|
||||
"{", // in case count does not reach min count request
|
||||
"}", //
|
||||
string.Empty); //endof count
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int TryGenerateCountSeq(
|
||||
int outer_level,
|
||||
ReadOnlySpan<RegexIR> ir_span,
|
||||
SourceGenContext sc,
|
||||
in SequenceEmitter<string> code,
|
||||
in List<SequenceEmitter<string>> local_func
|
||||
)
|
||||
{
|
||||
if (ir_span[0] is not RegexCountSeqIR count_seq) {
|
||||
return -1;
|
||||
}
|
||||
string seq = count_seq.Pattern;
|
||||
string literal = FormatStringLiteral(seq);
|
||||
bool is_greedy = count_seq.IsGreedy;
|
||||
int min = count_seq.Min;
|
||||
int max = count_seq.Max;
|
||||
int leave = is_greedy ? max : min;
|
||||
string counter = sc.RequireNewCounter();
|
||||
|
||||
// TODO cannot check real count outside the cycle
|
||||
_ = code.AppendLine($@"int {counter} = 0;", //
|
||||
@$"for(; {counter}<{leave}; {counter}++)", //
|
||||
"{", //
|
||||
@$"if(!{sc.RemainingSpan}.StartWith({literal}))", //
|
||||
"{", //
|
||||
$@"break;", //
|
||||
"}", //
|
||||
"}", //
|
||||
@$"if({counter}>{min})", //
|
||||
"{", // in case match
|
||||
sc.MakeRemainOffset(counter), //
|
||||
"}", //
|
||||
$@"else", //
|
||||
"{", // TODO fail here
|
||||
"}"); //
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int TryGenerateMatchSeq(
|
||||
int outer_level,
|
||||
ReadOnlySpan<RegexIR> ir_span,
|
||||
SourceGenContext sc,
|
||||
in SequenceEmitter<string> code,
|
||||
in List<SequenceEmitter<string>> local_func
|
||||
)
|
||||
{
|
||||
if (ir_span[0] is not RegexMatchSeqIR match_seq) {
|
||||
return -1;
|
||||
}
|
||||
string seq = match_seq.Pattern;
|
||||
string literal = FormatStringLiteral(seq);
|
||||
_ = code.AppendLine(@$"if({sc.RemainingSpan}.StartWith({literal}))")
|
||||
.AppendLine("{")
|
||||
.AppendLine($"{sc.StepResult} = true;")
|
||||
.AppendLine(sc.MakeRemainOffset(seq.Length))
|
||||
.AppendLine("}")
|
||||
.AppendLine("else")
|
||||
.AppendLine("{")//TODO fail here
|
||||
.AppendLine("break;")
|
||||
.AppendLine("}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int TryGenerateMatchSet(
|
||||
int outer_level,
|
||||
ReadOnlySpan<RegexIR> ir_span,
|
||||
SourceGenContext sc,
|
||||
in SequenceEmitter<string> code,
|
||||
in List<SequenceEmitter<string>> local_func
|
||||
)
|
||||
{
|
||||
if (ir_span[0] is not RegexMatchSetIR match_set) {
|
||||
return -1;
|
||||
}
|
||||
string set_source = match_set.Charset;
|
||||
RegexCharSet[] set_arr = FigureCharSetType(set_source, out bool accept);
|
||||
|
||||
_ = code.AppendLine(accept ? @$"if(" : @$"if(!(");
|
||||
string _case_code;
|
||||
RegexCharSet set;
|
||||
/*
|
||||
* in loop:
|
||||
* for( is cas1 1 ||
|
||||
* is case 2 ||
|
||||
* .....
|
||||
* is case last)
|
||||
* {
|
||||
* success here
|
||||
* }
|
||||
* else
|
||||
* {
|
||||
* fail case
|
||||
* }
|
||||
*/
|
||||
for (int i = 1; i < set_arr.Length - 1; i++)
|
||||
{
|
||||
set = set_arr[i];
|
||||
_case_code = GenLine(set, sc, "||");
|
||||
_ = code.AppendLine(_case_code);
|
||||
}
|
||||
set = set_arr[^1];
|
||||
_case_code = GenLine(set, sc, ")");
|
||||
_ = code.AppendLine(_case_code)
|
||||
.AppendLine("{")
|
||||
.AppendLine(sc.MakeRemainOffset(1)) // TODO case success
|
||||
.AppendLine("}")
|
||||
.AppendLine("else")
|
||||
.AppendLine("{")
|
||||
.AppendLine("break;") // TODO case fail
|
||||
.AppendLine("}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static string GenLine(
|
||||
RegexCharSet set,
|
||||
SourceGenContext sc,
|
||||
string end_of_line,
|
||||
string symbol_of_offset = ""
|
||||
)
|
||||
{
|
||||
string offset = string.IsNullOrEmpty(symbol_of_offset) ? "0" : $"{symbol_of_offset}";
|
||||
switch (set.Type)
|
||||
{
|
||||
case CharSetType.Single:
|
||||
string cases = set.Value;
|
||||
if (cases.Length > 8)
|
||||
{
|
||||
// more than 8 different chars, using string match
|
||||
string literal = FormatStringLiteral(cases);
|
||||
return $@"{literal}.Contains({sc.RemainingSpan}[{offset}]) {end_of_line}";
|
||||
} else
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
string literal;
|
||||
_ = sb.Append('(');
|
||||
for (int j = 0; j < cases.Length - 1; j++)
|
||||
{
|
||||
literal = FormatCharLiteral(cases[j]);
|
||||
_ = sb.Append(CultureInfo.InvariantCulture, $@"{sc.RemainingSpan}[{offset}] == {literal} ||");
|
||||
}
|
||||
literal = FormatCharLiteral(cases[^1]);
|
||||
_ = sb.Append(CultureInfo.InvariantCulture, $@"{sc.RemainingSpan}[{offset}] == {literal}")
|
||||
.Append(CultureInfo.InvariantCulture, @$") {end_of_line}");
|
||||
return sb.ToString();
|
||||
}
|
||||
case CharSetType.Range:
|
||||
string rule = set.Value;
|
||||
char lo = rule[0];
|
||||
char hi = rule[1];
|
||||
return $@"({sc.RemainingSpan}[{offset}] >= '{lo}' && {sc.RemainingSpan}[{offset}] <= '{hi}') {end_of_line}";
|
||||
|
||||
case CharSetType.Set:
|
||||
string accept_mark = set.Accept ? string.Empty : "!";
|
||||
return set.Value switch
|
||||
{
|
||||
"s" => $@"{accept_mark}char.IsWhiteSpace({sc.RemainingSpan}[{offset})] {end_of_line}",
|
||||
"d" => $@"{accept_mark}char.IsDigit({sc.RemainingSpan}[{offset})] {end_of_line}",
|
||||
"w" => $@"{accept_mark}IsCharWord({sc.RemainingSpan}[{offset})] {end_of_line}",
|
||||
_ => string.Empty
|
||||
};
|
||||
case CharSetType.None:
|
||||
return string.Empty;
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user