Using new file structure. Use new regexir display method. Fix line dependency error in prefix analysing.
Signed-off-by: Nitload <YoumuLiu_NSI@whv0t.onmicrosoft.com>
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="AbstractSyntaxTree"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using StgSharp.Collections;
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.Schema;
|
||||
|
||||
namespace StgSharp.RegularAnalysis.Abstraction
|
||||
{
|
||||
public sealed class AbstractSyntaxTree<TNode, TType> where TNode : ISyntaxNode<TNode, TType>
|
||||
where TType : unmanaged
|
||||
{
|
||||
|
||||
public HashSet<TNode> AllNodes => AllToken;
|
||||
|
||||
public int Count => AllToken.Count;
|
||||
|
||||
public TNode Root
|
||||
{
|
||||
get;
|
||||
internal set
|
||||
{
|
||||
if (AllToken.Contains(value)) {
|
||||
field = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HashSet<TNode> AllToken { get; } = new();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool AddNode(
|
||||
TNode node
|
||||
)
|
||||
{
|
||||
return AllToken.Add(node);
|
||||
}
|
||||
|
||||
public bool Contains(
|
||||
TNode node
|
||||
)
|
||||
{
|
||||
return AllToken.Contains(node);
|
||||
}
|
||||
|
||||
public IEnumerator<TNode> GetEnumerator()
|
||||
{
|
||||
return new TreeEnumerator<TNode, TType>(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class TreeEnumerator<TNode, TType>(
|
||||
TNode root
|
||||
) : IEnumerator<TNode> where TNode : ISyntaxNode<TNode, TType> where TType : unmanaged
|
||||
{
|
||||
|
||||
private bool _completed;
|
||||
|
||||
private int _state, _target; // 0: not started, 1: on right, 2: ended
|
||||
private readonly Stack<Record> _recycle = [];
|
||||
private readonly Stack<Record> _stack = [];
|
||||
|
||||
// private TNode _lRoot;
|
||||
private TNode _root = root;
|
||||
|
||||
internal TreeEnumerator(
|
||||
AbstractSyntaxTree<TNode, TType> tree
|
||||
)
|
||||
: this(tree.Root) { }
|
||||
|
||||
public int Depth => _stack.Count;
|
||||
|
||||
public TNode Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_stack.TryPeek(out Record? r)) {
|
||||
return r.Node;
|
||||
}
|
||||
return TNode.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_completed) {
|
||||
return false;
|
||||
}
|
||||
while (_stack.TryPeek(out Record? cur))
|
||||
{
|
||||
switch (cur.State)
|
||||
{
|
||||
case 0:
|
||||
cur.State++;
|
||||
if (TNode.IsNullOrEmpty(cur.Node.Left))
|
||||
{
|
||||
goto case 1;
|
||||
} else
|
||||
{
|
||||
_stack.Push(GetNewRecord(cur.Node.Left));
|
||||
return true;
|
||||
}
|
||||
case 1:
|
||||
cur.State++;
|
||||
if (TNode.IsNullOrEmpty(cur.Node.Right))
|
||||
{
|
||||
goto case 2;
|
||||
} else
|
||||
{
|
||||
_stack.Push(GetNewRecord(cur.Node.Right));
|
||||
return true;
|
||||
}
|
||||
case 2:
|
||||
_recycle.Push(_stack.Pop());
|
||||
if (TNode.IsNullOrEmpty(cur.Node.Next))
|
||||
{
|
||||
if (_stack.Count == 0)
|
||||
{
|
||||
_completed = true;
|
||||
return false;
|
||||
}
|
||||
} else
|
||||
{
|
||||
_stack.Push(GetNewRecord(cur.Node.Next));
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
_stack.Push(GetNewRecord(_root));
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_completed = false;
|
||||
_stack.Clear();
|
||||
_state = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skips the node and its subtree if the current node is the given node. Otherwise, do
|
||||
/// nothing.
|
||||
/// </summary>
|
||||
/// <param name="node">
|
||||
/// The node to skip.
|
||||
/// </param>
|
||||
public void SkipNode(
|
||||
TNode node
|
||||
)
|
||||
{
|
||||
if (_stack.TryPeek(out Record? r) && ReferenceEquals(r.Node, node))
|
||||
{
|
||||
_ = _stack.Pop();
|
||||
_recycle.Push(r);
|
||||
}
|
||||
}
|
||||
|
||||
public void SkipOnce()
|
||||
{
|
||||
if (_stack.TryPeek(out Record? cur))
|
||||
{
|
||||
switch (cur.State)
|
||||
{
|
||||
case 0:
|
||||
cur.State++;
|
||||
break;
|
||||
case 1:
|
||||
cur.State++;
|
||||
break;
|
||||
case 2:
|
||||
if (TNode.IsNullOrEmpty(cur.Node.Next))
|
||||
{
|
||||
_recycle.Push(_stack.Pop());
|
||||
if (_stack.Count == 0)
|
||||
{
|
||||
_completed = true;
|
||||
return;
|
||||
}
|
||||
} else
|
||||
{
|
||||
_stack.Push(GetNewRecord(cur.Node.Next));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Record GetNewRecord(
|
||||
TNode node
|
||||
)
|
||||
{
|
||||
if (_recycle.TryPop(out Record? r))
|
||||
{
|
||||
r.State = 0;
|
||||
r.Node = node;
|
||||
return r;
|
||||
}
|
||||
return new Record(node, 0);
|
||||
}
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
private class Record(
|
||||
TNode node,
|
||||
int state
|
||||
)
|
||||
{
|
||||
|
||||
public int State { get; set; } = state;
|
||||
|
||||
public TNode Node { get; set; } = node;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="CompileDepthState"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.RegularAnalysis.Abstraction
|
||||
{
|
||||
public interface ICompileDepthState
|
||||
{
|
||||
|
||||
string CurrentState { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class CompileDepthMark
|
||||
{
|
||||
|
||||
internal CompileDepthMark(
|
||||
int operatorBegin,
|
||||
int operandBegin,
|
||||
int usage
|
||||
)
|
||||
{
|
||||
OperatorBegin = operatorBegin;
|
||||
OperandBegin = operandBegin;
|
||||
Usage = usage;
|
||||
}
|
||||
|
||||
internal CompileDepthMark(
|
||||
int operatorBegin,
|
||||
int operandBegin,
|
||||
int usage,
|
||||
ICompileDepthState stateCache
|
||||
)
|
||||
{
|
||||
OperatorBegin = operatorBegin;
|
||||
OperandBegin = operandBegin;
|
||||
Usage = usage;
|
||||
StateCache = stateCache;
|
||||
}
|
||||
|
||||
public bool IsBeginOfBlock { get; internal set; }
|
||||
|
||||
public ICompileDepthState StateCache { get; internal set; }
|
||||
|
||||
public int OperandBegin { get; internal set; }
|
||||
|
||||
public int OperatorBegin { get; internal set; }
|
||||
|
||||
public int Usage { get; internal set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="CompileStack"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using StgSharp.Collections;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.RegularAnalysis.Abstraction
|
||||
{
|
||||
public partial class CompileStack<TNode, TLabel> where TNode : ISyntaxNode<TNode, TLabel>
|
||||
where TLabel : unmanaged
|
||||
{
|
||||
|
||||
private readonly RandomAccessibleStack<DepthMark> _depthMarks = [];
|
||||
private readonly RandomAccessibleStack<TNode> _operands = [];
|
||||
private readonly RandomAccessibleStack<TNode> _operators = [];
|
||||
|
||||
public CompileStack()
|
||||
{
|
||||
_depthMarks.Push(new DepthMark(0, 0, 0));
|
||||
}
|
||||
|
||||
public int OperandInDepthCount => _operands.Count - _depthMarks.Peek().OperandPos;
|
||||
|
||||
public int OperatorInDepthCount => _operators.Count - _depthMarks.Peek().OperatorPos;
|
||||
|
||||
public int Depth => _depthMarks.Count;
|
||||
|
||||
private record struct DepthMark(
|
||||
int OperandPos,
|
||||
int OperatorPos,
|
||||
int Mark
|
||||
);
|
||||
|
||||
#region operator
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TNode PopOperator(
|
||||
)
|
||||
{
|
||||
if (OperatorInDepthCount > 0) {
|
||||
return _operators.Pop();
|
||||
}
|
||||
return TNode.Empty;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void PushOperator(
|
||||
TNode operatorNode
|
||||
)
|
||||
{
|
||||
_operators.Push(operatorNode);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryPopOperator(
|
||||
out TNode op
|
||||
)
|
||||
{
|
||||
if (OperatorInDepthCount > 0)
|
||||
{
|
||||
op = _operators.Pop();
|
||||
return true;
|
||||
}
|
||||
op = TNode.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryPeekOperator(
|
||||
out TNode op
|
||||
)
|
||||
{
|
||||
if (OperatorInDepthCount > 0)
|
||||
{
|
||||
op = _operators.Peek();
|
||||
return true;
|
||||
}
|
||||
op = TNode.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region operand
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TNode PopOperand(
|
||||
)
|
||||
{
|
||||
if (OperandInDepthCount > 0) {
|
||||
return _operands.Pop();
|
||||
}
|
||||
return TNode.Empty;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void PushOperand(
|
||||
TNode operatorNode
|
||||
)
|
||||
{
|
||||
_operands.Push(operatorNode);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryPopOperand(
|
||||
out TNode op
|
||||
)
|
||||
{
|
||||
if (OperandInDepthCount > 0)
|
||||
{
|
||||
op = _operands.Pop();
|
||||
return true;
|
||||
}
|
||||
op = TNode.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryPeekOperand(
|
||||
out TNode op
|
||||
)
|
||||
{
|
||||
if (OperandInDepthCount > 0)
|
||||
{
|
||||
op = _operands.Peek();
|
||||
return true;
|
||||
}
|
||||
op = TNode.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region depth
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void DecreaseDepth(
|
||||
)
|
||||
{
|
||||
if (Depth > 1)
|
||||
{
|
||||
DepthMark mark = _depthMarks.Pop();
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void IncreaseDepth(
|
||||
int mark
|
||||
)
|
||||
{
|
||||
_depthMarks.Push(new DepthMark(_operands.Count, _operators.Count, mark));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="ISyntaxNode"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using StgSharp.Collections;
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace StgSharp.RegularAnalysis.Abstraction
|
||||
{
|
||||
public interface ITrinitySyntaxNode<TNode, TLabel> : ISyntaxNode<TNode, TLabel>
|
||||
where TNode : ISyntaxNode<TNode, TLabel>
|
||||
where TLabel : unmanaged
|
||||
{
|
||||
|
||||
TNode Third { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public interface ISyntaxNode<TNode, TLabel> where TNode : ISyntaxNode<TNode, TLabel>
|
||||
where TLabel : unmanaged
|
||||
{
|
||||
|
||||
long NodeFlag { get; }
|
||||
|
||||
TLabel EqualityTypeConvert { get; }
|
||||
|
||||
TNode Previous { get; set; }
|
||||
|
||||
TNode Next { get; set; }
|
||||
|
||||
TNode Left { get; set; }
|
||||
|
||||
TNode Right { get; set; }
|
||||
|
||||
TNode Parent { get; set; }
|
||||
|
||||
static abstract TNode Empty { get; }
|
||||
|
||||
Token<TLabel> Source { get; }
|
||||
|
||||
protected internal int EnumState { get; set; }
|
||||
|
||||
void AppendNode(
|
||||
TNode nextToken
|
||||
);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static virtual bool IsNullOrEmpty(
|
||||
TNode node
|
||||
)
|
||||
{
|
||||
return node is null || node.Equals(TNode.Empty);
|
||||
}
|
||||
|
||||
void PrependNode(
|
||||
TNode previousNode
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="ITokenParser"
|
||||
// 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 StgSharp.RegularAnalysis.Abstraction
|
||||
{
|
||||
public abstract class TokenParser<TIn, TOut> : ITokenReader<TOut> where TIn : unmanaged
|
||||
where TOut : unmanaged
|
||||
{
|
||||
|
||||
public abstract bool IsEmpty { get; }
|
||||
|
||||
protected internal ITokenReader<TIn> Source { get; internal set; }
|
||||
|
||||
public abstract Token<TOut> ReadToken();
|
||||
|
||||
public abstract bool TryReadToken(
|
||||
out Token<TOut> t
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="ITokenReader"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
|
||||
namespace StgSharp.RegularAnalysis.Abstraction
|
||||
{
|
||||
public static class TokenReader
|
||||
{
|
||||
|
||||
public static TokenParser<TIn, TOut> Pipe<TIn, TOut>(
|
||||
this ITokenReader<TIn> source,
|
||||
Func<TokenParser<TIn, TOut>> builder
|
||||
) where TIn : unmanaged where TOut : unmanaged
|
||||
{
|
||||
TokenParser<TIn, TOut> parser = builder();
|
||||
parser.Source = source;
|
||||
return parser;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface ITokenReader<TLabel> where TLabel : unmanaged
|
||||
{
|
||||
|
||||
bool IsEmpty { get; }
|
||||
|
||||
Token<TLabel> ReadToken();
|
||||
|
||||
bool TryReadToken(
|
||||
out Token<TLabel> t
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="SequenceEmitter"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using StgSharp.RegularAnalysis.Text;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.RegularAnalysis.Abstraction
|
||||
{
|
||||
public static class SequenceEmitterExtensions
|
||||
{
|
||||
|
||||
public static SequenceEmitter<string> AppendLine(
|
||||
this SequenceEmitter<string> emitter
|
||||
)
|
||||
{
|
||||
return emitter is null ? null! : emitter.Append(string.Empty);
|
||||
}
|
||||
|
||||
public static SequenceEmitter<string> AppendLine(
|
||||
this SequenceEmitter<string> emitter,
|
||||
string line
|
||||
)
|
||||
{
|
||||
return emitter is null ?
|
||||
null! :
|
||||
emitter.Append(string.IsNullOrEmpty(line) ? string.Empty : line);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public sealed class SequenceEmitter<T>
|
||||
{
|
||||
|
||||
private bool _isRoot;
|
||||
|
||||
private readonly Node _head;
|
||||
private Node _tail;
|
||||
|
||||
public SequenceEmitter()
|
||||
{
|
||||
_isRoot = true;
|
||||
_head = new();
|
||||
_tail = _head;
|
||||
}
|
||||
|
||||
public int Count { get; private set; }
|
||||
|
||||
private static bool IsString { get; } = typeof(T) == typeof(string);
|
||||
|
||||
public SequenceEmitter<T> Append(
|
||||
T unit
|
||||
)
|
||||
{
|
||||
if (!_isRoot) {
|
||||
throw new InvalidOperationException("This emitter has been appended to another emitter. It is now readonly.");
|
||||
}
|
||||
_tail.Add(unit);
|
||||
Count++;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SequenceEmitter<T> Append(
|
||||
SequenceEmitter<T> emitter
|
||||
)
|
||||
{
|
||||
if (emitter == null || emitter.Count == 0) {
|
||||
return this;
|
||||
}
|
||||
if (ReferenceEquals(this, emitter)) {
|
||||
throw new InvalidOperationException("Cannot append emitter to itself.");
|
||||
}
|
||||
|
||||
if (!emitter._isRoot) {
|
||||
throw new InvalidOperationException("The emitter has been appended to another emitter.");
|
||||
}
|
||||
_tail.Next = emitter._head;
|
||||
_tail = emitter._tail;
|
||||
Count += emitter.Count;
|
||||
emitter._isRoot = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return _isRoot ?
|
||||
new Enumerator(this) :
|
||||
throw new InvalidOperationException("This emitter has been appended to another emitter. You should not get its enumerator.");
|
||||
}
|
||||
|
||||
public List<T> ToList()
|
||||
{
|
||||
List<T> result = new(Count);
|
||||
Node current = _head;
|
||||
while (current != null)
|
||||
{
|
||||
result.AddRange(current.IRList);
|
||||
current = current.Next!;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#pragma warning disable CA1063
|
||||
#pragma warning disable CA1816
|
||||
public class Enumerator(
|
||||
[NotNull] SequenceEmitter<T> emitter
|
||||
) : IEnumerator<T>
|
||||
{
|
||||
|
||||
private int _index = -1;
|
||||
|
||||
private Node _current = null!;
|
||||
private readonly Node _root = emitter._head;
|
||||
|
||||
public T Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_current is null || _index < 0 || _index >= _current.Count) {
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return _current[_index];
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
_current ??= _root;
|
||||
int index = _index + 1;
|
||||
if (index < _current.Count)
|
||||
{
|
||||
_index = index;
|
||||
return true;
|
||||
}
|
||||
Node current = _current.Next!;
|
||||
if (current is null || ReferenceEquals(current, _root))
|
||||
{
|
||||
_index = -1;
|
||||
return false;
|
||||
}
|
||||
_current = current;
|
||||
_index = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_current = _root;
|
||||
_index = -1;
|
||||
}
|
||||
|
||||
object IEnumerator.Current => Current!;
|
||||
|
||||
}
|
||||
|
||||
#pragma warning restore CA1063
|
||||
#pragma warning restore CA1816
|
||||
|
||||
private sealed class Node
|
||||
{
|
||||
|
||||
public T this[
|
||||
int index
|
||||
]
|
||||
{
|
||||
get => IRList[index];
|
||||
set => IRList[index] = value;
|
||||
}
|
||||
|
||||
public Node? Next { get; set; }
|
||||
|
||||
public int Count => IRList.Count;
|
||||
|
||||
public List<T> IRList { get; } = [];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(
|
||||
T ir
|
||||
)
|
||||
{
|
||||
IRList.Add(ir);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="Token"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.RegularAnalysis.Abstraction
|
||||
{
|
||||
public readonly struct Token<TLabel> where TLabel : unmanaged
|
||||
{
|
||||
|
||||
public readonly int Column;
|
||||
public readonly int Line;
|
||||
public readonly string Value;
|
||||
public readonly TLabel Flag;
|
||||
|
||||
public Token()
|
||||
{
|
||||
Column = 0;
|
||||
Line = 0;
|
||||
Value = string.Empty;
|
||||
Flag = default;
|
||||
}
|
||||
|
||||
public Token(
|
||||
string chars,
|
||||
int lineNumber,
|
||||
int columnNumber,
|
||||
TLabel flag
|
||||
)
|
||||
{
|
||||
Value = chars;
|
||||
Line = lineNumber;
|
||||
Column = columnNumber;
|
||||
Flag = flag;
|
||||
}
|
||||
|
||||
public static Token<TLabel> Empty { get; } = new Token<TLabel>();
|
||||
|
||||
public override bool Equals(
|
||||
[NotNullWhen(true)] object? obj
|
||||
)
|
||||
{
|
||||
return base.Equals(obj);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Token<TLabel> ReLocate(
|
||||
int line,
|
||||
int column
|
||||
)
|
||||
{
|
||||
return new Token<TLabel>(Value, line, column, Flag);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Token<TNewLabel> Remark<TNewLabel>(
|
||||
TNewLabel label
|
||||
) where TNewLabel : unmanaged
|
||||
{
|
||||
return new Token<TNewLabel>(Value, Line, Column, label);
|
||||
}
|
||||
|
||||
public static bool operator !=(
|
||||
Token<TLabel> left,
|
||||
Token<TLabel> right
|
||||
)
|
||||
{
|
||||
return !left.Value.SequenceEqual(right.Value) ||
|
||||
left.Line != right.Line ||
|
||||
left.Column != right.Column ||
|
||||
!left.Flag.Equals(right.Flag);
|
||||
}
|
||||
|
||||
public static bool operator ==(
|
||||
Token<TLabel> left,
|
||||
Token<TLabel> right
|
||||
)
|
||||
{
|
||||
return left.Value.SequenceEqual(right.Value) &&
|
||||
left.Line == right.Line &&
|
||||
left.Column == right.Column &&
|
||||
left.Flag.Equals(right.Flag);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="Global"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
[assembly: InternalsVisibleTo("StgSharpDebug")]
|
||||
@@ -0,0 +1,30 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="GroupIndex"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace StgSharp.RegularAnalysis
|
||||
{
|
||||
public record struct GroupIndex
|
||||
{
|
||||
|
||||
public GroupIndex(
|
||||
int index,
|
||||
string name
|
||||
)
|
||||
{
|
||||
Index = index;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public int Index { get; internal set; }
|
||||
|
||||
public string Name { get; internal set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="SourceGenerate"
|
||||
// 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 StgSharp.RegularAnalysis
|
||||
{
|
||||
internal class SourceGenerate { }
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="bin\**" />
|
||||
<EmbeddedResource Remove="bin\**" />
|
||||
<None Remove="bin\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StgSharp.Common\StgSharp.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="bin\x64\Debug\net8.0\StgSharp.RegularAnalysis.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="$(OutputPath)$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||
<None Remove="bin\x64\Debug\net8.0\StgSharp.RegularAnalysis.pdb" />
|
||||
<None Include="$(OutputPath)$(AssemblyName).pdb" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" Condition="Exists('$(OutputPath)$(AssemblyName).pdb')" />
|
||||
<None Remove="bin\x64\Debug\net8.0\StgSharp.Common.dll" />
|
||||
<None Include="$(OutputPath)StgSharp.Common.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" Condition="Exists('$(OutputPath)StgSharp.Common.dll')" />
|
||||
<None Remove="bin\x64\Debug\net8.0\StgSharp.Common.pdb" />
|
||||
<None Include="$(OutputPath)StgSharp.Common.pdb" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" Condition="Exists('$(OutputPath)StgSharp.Common.pdb')" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,159 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegexAnalyzer.ASTGenerate"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using StgSharp.RegularAnalysis.Abstraction;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.RegularAnalysis.Text
|
||||
{
|
||||
public partial class RegexAnalyzer
|
||||
{
|
||||
|
||||
private static AbstractSyntaxTree<RegexAstNode, RegexElementLabel> GenerateAST(
|
||||
string _source
|
||||
)
|
||||
{
|
||||
CompileStack<RegexAstNode, RegexElementLabel> _stack = new();
|
||||
AbstractSyntaxTree<RegexAstNode, RegexElementLabel> _tree = new();
|
||||
RegexTokenReader reader = new(_source);
|
||||
TokenParser<RegexElementLabel, RegexElementLabel> lexer = reader.Pipe(() => new RegexTokenParser());
|
||||
|
||||
while (lexer.TryReadToken(out Token<RegexElementLabel> token))
|
||||
{
|
||||
// Console.WriteLine(token.Value);
|
||||
if ((token.Flag & RegexElementLabel.SEQUENCE) != 0)
|
||||
{
|
||||
// char or charset
|
||||
_stack.PushOperand(new RegexAstNode(token));
|
||||
} else if ((token.Flag & RegexElementLabel.GROUP_BEGIN) != 0)
|
||||
{
|
||||
_stack.PushOperator(new RegexAstNode(token));
|
||||
_stack.IncreaseDepth(0);
|
||||
} else if ((token.Flag & RegexElementLabel.GROUP_END) != 0)
|
||||
{
|
||||
// close all nodes here
|
||||
RegexAstNode op;
|
||||
while (_stack.TryPopOperator(out op)) {
|
||||
ProcessOperator(op);
|
||||
}
|
||||
if (_stack.OperandInDepthCount != 1) {
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
_stack.DecreaseDepth();
|
||||
op = _stack.PopOperator();
|
||||
if (op.Source.Flag != RegexElementLabel.GROUP_BEGIN) {
|
||||
throw new InvalidOperationException("Unmatched group begin and end symbol");
|
||||
}
|
||||
if (op.Source.Value.Length > 1)
|
||||
{
|
||||
op.Right = _stack.PopOperand();
|
||||
_stack.PushOperand(op);
|
||||
}
|
||||
} else if ((token.Flag & RegexElementLabel.OPERATOR) != 0)
|
||||
{
|
||||
if (_stack.OperatorInDepthCount == 0)
|
||||
{
|
||||
// the first operator in stack
|
||||
_stack.PushOperator(new RegexAstNode(token));
|
||||
} else
|
||||
{
|
||||
// process operator by precedence
|
||||
while (_stack.TryPeekOperator(out RegexAstNode topOp))
|
||||
{
|
||||
(bool Valid, int Value) cmp = RegexAstNode.ComparePrecedence(topOp.Source.Flag, token.Flag);
|
||||
|
||||
if (!cmp.Valid) {
|
||||
throw new InvalidOperationException("Operators with same precedence are not supported.");
|
||||
}
|
||||
if (cmp.Value < 0)
|
||||
{
|
||||
_stack.PopOperator();
|
||||
ProcessOperator(topOp);
|
||||
continue;
|
||||
} else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
_stack.PushOperator(new RegexAstNode(token));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_stack.Depth == 1)
|
||||
{
|
||||
if (_stack.OperandInDepthCount != 1 || _stack.OperatorInDepthCount != 0)
|
||||
{
|
||||
while (_stack.TryPopOperator(out RegexAstNode? op)) {
|
||||
ProcessOperator(op);
|
||||
}
|
||||
}
|
||||
if (_stack.OperandInDepthCount == 1 && _stack.OperatorInDepthCount == 0)
|
||||
{
|
||||
_tree.Root = _stack.PopOperand();
|
||||
return _tree;
|
||||
}
|
||||
}
|
||||
throw new InvalidOperationException("Invalid regular expression syntax.");
|
||||
|
||||
|
||||
void ProcessOperator(
|
||||
RegexAstNode op
|
||||
)
|
||||
{
|
||||
if (op.EqualityTypeConvert == RegexElementLabel.CONCAT)
|
||||
{
|
||||
if (_stack.TryPopOperand(out RegexAstNode? _1) &&
|
||||
_stack.TryPopOperand(out RegexAstNode? _2))
|
||||
{
|
||||
op.Left = _2;
|
||||
op.Right = _1;
|
||||
_stack.PushOperand(op);
|
||||
_ = _tree.AddNode(op);
|
||||
_ = _tree.AddNode(_1);
|
||||
_ = _tree.AddNode(_2);
|
||||
} else
|
||||
{
|
||||
throw new InvalidOperationException("Insufficient operands for CONCAT operator.");
|
||||
}
|
||||
} else if (op.EqualityTypeConvert == RegexElementLabel.COUNT)
|
||||
{
|
||||
if (_stack.TryPopOperand(out RegexAstNode? _1))
|
||||
{
|
||||
op.Right = _1;
|
||||
_stack.PushOperand(op);
|
||||
_ = _tree.AddNode(_1);
|
||||
_ = _tree.AddNode(op);
|
||||
} else
|
||||
{
|
||||
throw new InvalidOperationException("Insufficient operands for CONCAT operator.");
|
||||
}
|
||||
} else if (op.EqualityTypeConvert == RegexElementLabel.ALT)
|
||||
{
|
||||
if (_stack.TryPopOperand(out RegexAstNode? _1) &&
|
||||
_stack.TryPopOperand(out RegexAstNode? _2))
|
||||
{
|
||||
op.Left = _2;
|
||||
op.Right = _1;
|
||||
_stack.PushOperand(op);
|
||||
_ = _tree.AddNode(op);
|
||||
_ = _tree.AddNode(_1);
|
||||
_ = _tree.AddNode(_2);
|
||||
} else
|
||||
{
|
||||
throw new InvalidOperationException("Insufficient operands for CONCAT operator.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegexAnalyzer.ASTOptimize"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using StgSharp.RegularAnalysis.Abstraction;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Security;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Permissions;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace StgSharp.RegularAnalysis.Text
|
||||
{
|
||||
public partial class RegexAnalyzer
|
||||
{
|
||||
|
||||
private static RegexAstNode FlattenAlt(
|
||||
RegexAstNode root
|
||||
)
|
||||
{
|
||||
TreeEnumerator<RegexAstNode, RegexElementLabel> enumerator = new(root);
|
||||
List<RegexAstNode> cases = [];
|
||||
RegexAstNode current;
|
||||
_ = enumerator.MoveNext(); // skip root
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
current = enumerator.Current;
|
||||
if (current.EqualityTypeConvert != RegexElementLabel.ALT)
|
||||
{
|
||||
cases.Add(current);
|
||||
enumerator.SkipNode(current);
|
||||
}
|
||||
current.Parent.RemoveChild(current);
|
||||
}
|
||||
for (int i = 0; i < cases.Count - 1; i++)
|
||||
{
|
||||
cases[i].AppendNode(cases[i + 1]);
|
||||
cases[i].Parent = root;
|
||||
}
|
||||
cases[^1].Parent = root;
|
||||
return cases[0];
|
||||
}
|
||||
|
||||
private static bool MergeConcat(
|
||||
RegexAstNode root
|
||||
)
|
||||
{
|
||||
if (root.EqualityTypeConvert != RegexElementLabel.CONCAT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* concat → 'a'
|
||||
* ╱
|
||||
* 'a'
|
||||
*/
|
||||
if ((root.Left.EqualityTypeConvert & RegexElementLabel.SEQUENCE) != 0 &&
|
||||
RegexAstNode.IsNullOrEmpty(root.Right))
|
||||
{
|
||||
root.RemoveAndAsChild(true);
|
||||
return true;
|
||||
}
|
||||
if ((root.Right.EqualityTypeConvert & RegexElementLabel.SEQUENCE) != 0 &&
|
||||
RegexAstNode.IsNullOrEmpty(root.Left))
|
||||
{
|
||||
root.RemoveAndAsChild(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* concat → concat
|
||||
* ╱ ╲ /
|
||||
* 'a' concat "abc"
|
||||
* / \
|
||||
* 'b' 'c'
|
||||
*/
|
||||
List<string> buffer = [];
|
||||
int length = 0;
|
||||
RegexAstNode right = root.Right;
|
||||
RegexAstNode left = root.Left;
|
||||
if (left.EqualityTypeConvert is not RegexElementLabel.UNIT_SPAN and
|
||||
not RegexElementLabel.UNIT) {
|
||||
return false;
|
||||
}
|
||||
string str = left.Value;
|
||||
buffer.Add(str);
|
||||
length += str.Length;
|
||||
while (!RegexAstNode.IsNullOrEmpty(right) &&
|
||||
right.EqualityTypeConvert == RegexElementLabel.CONCAT)
|
||||
{
|
||||
left = right.Left;
|
||||
if (left.EqualityTypeConvert is not RegexElementLabel.UNIT_SPAN and
|
||||
not RegexElementLabel.UNIT)
|
||||
{
|
||||
break;
|
||||
}
|
||||
str = left.Value;
|
||||
buffer.Add(str);
|
||||
length += str.Length;
|
||||
root.Right = right.Right;
|
||||
right.Parent = RegexAstNode.Empty;
|
||||
right = root.Right;
|
||||
right.Parent = root;
|
||||
}
|
||||
if (right.EqualityTypeConvert is RegexElementLabel.UNIT_SPAN or
|
||||
RegexElementLabel.UNIT)
|
||||
{
|
||||
root.Right = RegexAstNode.Empty;
|
||||
buffer.Add(right.Value);
|
||||
length += right.Value.Length;
|
||||
}
|
||||
str = string.Create(length, buffer, static(
|
||||
span,
|
||||
buffer
|
||||
) => {
|
||||
int offset = 0;
|
||||
for (int i = 0; i < buffer.Count; i++)
|
||||
{
|
||||
buffer[i].AsSpan().CopyTo(span[offset..]);
|
||||
offset += buffer[i].Length;
|
||||
}
|
||||
});
|
||||
root.Left = new RegexAstNode(new Token<RegexElementLabel>(str, left.Source.Line,
|
||||
left.Source.Column,
|
||||
str.Length == 1 ?
|
||||
RegexElementLabel.UNIT :
|
||||
RegexElementLabel.UNIT_SPAN));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void OptimizeTree(
|
||||
AbstractSyntaxTree<RegexAstNode, RegexElementLabel> tree
|
||||
)
|
||||
{
|
||||
// phase0: scan container and remove no parent nodes
|
||||
_ = tree.AllNodes.RemoveWhere(node => node.Parent == null && node != tree.Root);
|
||||
TreeEnumerator<RegexAstNode, RegexElementLabel> enumerator = new(tree.Root);
|
||||
RegexAstNode current;
|
||||
|
||||
// phase1: ALT flatten
|
||||
// phase2: CONCAT normalize
|
||||
int i = 0;
|
||||
List<RegexAstNode> altNodes = [];
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
current = enumerator.Current;
|
||||
if (current.EqualityTypeConvert == RegexElementLabel.ALT)
|
||||
{
|
||||
RegexAstNode union = FlattenAlt(current);
|
||||
altNodes.Add(current);
|
||||
current.Right = union;
|
||||
current.Left = RegexAstNode.Empty;
|
||||
} else if (current.EqualityTypeConvert == RegexElementLabel.CONCAT) {
|
||||
RotateConcat(current);
|
||||
}
|
||||
}
|
||||
|
||||
// phase3: then ALT union
|
||||
foreach (RegexAstNode item in altNodes)
|
||||
{
|
||||
List<RegexAstNode> cases = [];
|
||||
RegexAstNode _case = item.Right;
|
||||
do
|
||||
{
|
||||
cases.Add(_case);
|
||||
_case = _case.Next;
|
||||
} while (!RegexAstNode.IsNullOrEmpty(_case));
|
||||
RegexAstNode newAlt = MergeAlt(cases);
|
||||
item.ReplaceBy(newAlt);
|
||||
}
|
||||
|
||||
// return;
|
||||
|
||||
// phase4: CONCAT merge
|
||||
enumerator.Reset();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
// suppose all concat is now in normalized form,
|
||||
// we can merge adjacent unit nodes into a single unit node with string value
|
||||
current = enumerator.Current;
|
||||
if (current.EqualityTypeConvert == RegexElementLabel.CONCAT)
|
||||
{
|
||||
if (MergeConcat(current)) {
|
||||
enumerator.SkipOnce();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void RotateConcat(
|
||||
RegexAstNode root
|
||||
)
|
||||
{
|
||||
RegexAstNode left = root.Left;
|
||||
while (!RegexAstNode.IsNullOrEmpty(left) &&
|
||||
left.EqualityTypeConvert == RegexElementLabel.CONCAT)
|
||||
{
|
||||
/*
|
||||
* root → root
|
||||
* ╱ ╲ ╱ ╲
|
||||
* left c a left
|
||||
* ╱ ╲ ╱ ╲
|
||||
* a b b c
|
||||
*/
|
||||
RegexAstNode a = left.Left;
|
||||
RegexAstNode b = left.Right;
|
||||
RegexAstNode c = root.Right;
|
||||
root.Left = a;
|
||||
if (RegexAstNode.IsNullOrEmpty(a)) {
|
||||
a.Parent = root;
|
||||
}
|
||||
|
||||
root.Right = left;
|
||||
left.Left = b;
|
||||
left.Right = c;
|
||||
if (RegexAstNode.IsNullOrEmpty(b)) {
|
||||
b.Parent = left;
|
||||
}
|
||||
|
||||
if (RegexAstNode.IsNullOrEmpty(c)) {
|
||||
c.Parent = left;
|
||||
}
|
||||
left = root.Left;
|
||||
}
|
||||
}
|
||||
|
||||
#region merge alt
|
||||
|
||||
/// <summary>
|
||||
/// Get union beginning part of cases
|
||||
/// </summary>
|
||||
/// <param name="cases">
|
||||
///
|
||||
/// </param>
|
||||
/// <returns>
|
||||
///
|
||||
/// </returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static RegexAstNode MergeAlt(
|
||||
List<RegexAstNode> cases
|
||||
)
|
||||
{
|
||||
UnionAlt union = new(0, cases);
|
||||
RegexAstNode root = union.AsNormal();
|
||||
return root;
|
||||
}
|
||||
|
||||
private class UnionAlt : RegexAstNode
|
||||
{
|
||||
|
||||
private const int MaxLevel = 8;
|
||||
|
||||
private UnionAlt(
|
||||
int level,
|
||||
RegexAstNode node
|
||||
)
|
||||
: base(node.Source)
|
||||
{
|
||||
Level = level;
|
||||
Cases = [];
|
||||
|
||||
RegexAstNode right = IsNullOrEmpty(node.Right) ?
|
||||
new RegexAstNode(
|
||||
new Token<RegexElementLabel>(node.Value, 0, node.Source.Column,
|
||||
RegexElementLabel.UNIT)
|
||||
) :
|
||||
node.Right;
|
||||
Cases.Add(right);
|
||||
|
||||
node.PrependNode(Empty);
|
||||
node.AppendNode(Empty);
|
||||
UnionBegin = node;
|
||||
UnionEnd = node;
|
||||
}
|
||||
|
||||
public UnionAlt(
|
||||
int level,
|
||||
List<RegexAstNode> c
|
||||
)
|
||||
: base(c[0].Source)
|
||||
{
|
||||
Cases = c;
|
||||
Level = level;
|
||||
}
|
||||
|
||||
public List<RegexAstNode> Cases { get; set; }
|
||||
|
||||
public RegexAstNode UnionBegin { get; set; }
|
||||
|
||||
public RegexAstNode UnionEnd { get; set; }
|
||||
|
||||
private int Level { get; set; }
|
||||
|
||||
public RegexAstNode AsNormal()
|
||||
{
|
||||
if (Level >= MaxLevel)
|
||||
{
|
||||
goto build;
|
||||
}
|
||||
merge:
|
||||
List<RegexAstNode> buffer = [];
|
||||
for (int i = 0; i < Cases.Count; i++)
|
||||
{
|
||||
if ((Cases[i].EqualityTypeConvert & RegexElementLabel.SEQUENCE) != 0)
|
||||
{
|
||||
RegexAstNode concat = new(new Token<RegexElementLabel>(string.Empty, 0,
|
||||
Cases[i].Source.Column,
|
||||
RegexElementLabel.CONCAT))
|
||||
{
|
||||
Left = Cases[i]
|
||||
};
|
||||
Cases[i].Parent = concat;
|
||||
concat.Right = Empty;
|
||||
Cases[i] = concat;
|
||||
}
|
||||
RegexAstNode current = Cases[i];
|
||||
int j = 0;
|
||||
for (; j < buffer.Count; j++)
|
||||
{
|
||||
if (CanMerge(current, buffer[j]))
|
||||
{
|
||||
// find a case in buffer that can merge with current case,
|
||||
// then merge them and break
|
||||
RegexAstNode b = buffer[j];
|
||||
if (b is not UnionAlt alt)
|
||||
{
|
||||
alt = new UnionAlt(Level + 1, b);
|
||||
alt.AddCase(current);
|
||||
buffer[j] = alt;
|
||||
} else
|
||||
{
|
||||
alt.AddCase(current);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j >= buffer.Count) {
|
||||
buffer.Add(current);
|
||||
}
|
||||
}
|
||||
if (Cases.Count != buffer.Count) {
|
||||
Cases = buffer;
|
||||
}
|
||||
|
||||
if (buffer.Count == 1)
|
||||
{
|
||||
// if there is only one case,
|
||||
// then we suppose that its contained cases has a same beginning,
|
||||
// so we attach it to UnionEnd and stay level unchanged
|
||||
UnionAlt alt = (Cases[0] as UnionAlt)!;
|
||||
if (IsNullOrEmpty(UnionBegin))
|
||||
{
|
||||
UnionBegin = alt.UnionBegin;
|
||||
UnionEnd = alt.UnionEnd;
|
||||
} else
|
||||
{
|
||||
alt.UnionBegin.Parent = UnionEnd;
|
||||
UnionEnd.Right = alt.UnionBegin;
|
||||
UnionEnd = alt.UnionBegin;
|
||||
}
|
||||
|
||||
Cases = alt.Cases;
|
||||
goto merge;
|
||||
}
|
||||
|
||||
build:
|
||||
|
||||
// suppose that from UnionBegin to UnionEnd is a normal form without union
|
||||
for (int i = 0; i < Cases.Count; i++)
|
||||
{
|
||||
if (Cases[i] is UnionAlt union) {
|
||||
Cases[i] = union.AsNormal();
|
||||
}
|
||||
}
|
||||
|
||||
RegexAstNode branch;
|
||||
if (IsNullOrEmpty(UnionEnd))
|
||||
{
|
||||
branch = new RegexAstNode(new Token<RegexElementLabel>(string.Empty, 0,
|
||||
Source.Column,
|
||||
RegexElementLabel.ALT));
|
||||
UnionBegin = branch;
|
||||
UnionEnd = branch;
|
||||
} else
|
||||
{
|
||||
branch = new RegexAstNode(new Token<RegexElementLabel>(string.Empty, 0,
|
||||
UnionEnd.Source.Column,
|
||||
RegexElementLabel.ALT));
|
||||
UnionEnd.Right = branch;
|
||||
branch.Parent = UnionEnd;
|
||||
UnionEnd = branch;
|
||||
}
|
||||
|
||||
for (int i = 0; i < Cases.Count - 1; i++)
|
||||
{
|
||||
Cases[i] ??= NewEmptyNode();
|
||||
Cases[i].AppendNode(Cases[i + 1]);
|
||||
Cases[i].Parent = UnionEnd;
|
||||
}
|
||||
UnionEnd.Right = Cases[0];
|
||||
return UnionBegin;
|
||||
}
|
||||
|
||||
private void AddCase(
|
||||
RegexAstNode _case
|
||||
)
|
||||
{
|
||||
RegexAstNode right = IsNullOrEmpty(_case.Right) ?
|
||||
new RegexAstNode(
|
||||
new Token<RegexElementLabel>(_case.Value, 0, _case.Source.Column,
|
||||
RegexElementLabel.UNIT)
|
||||
) :
|
||||
_case.Right;
|
||||
Cases.Add(right);
|
||||
}
|
||||
|
||||
private static bool CanMerge(
|
||||
RegexAstNode origin,
|
||||
RegexAstNode target
|
||||
)
|
||||
{
|
||||
// suppose that origin is not a union,
|
||||
if (target is UnionAlt alt)
|
||||
{
|
||||
// then we suppose that alt has only one are same
|
||||
return origin .EqualityTypeConvert == RegexElementLabel.CONCAT &&
|
||||
(origin.Left.EqualityTypeConvert & RegexElementLabel.SINGLE) != 0 &&
|
||||
origin.Left.EqualityTypeConvert == alt.UnionEnd.Left.EqualityTypeConvert &&
|
||||
origin.Left.Value == alt.UnionEnd.Left.Value;
|
||||
} else
|
||||
{
|
||||
return origin.EqualityTypeConvert == RegexElementLabel.CONCAT &&
|
||||
target.EqualityTypeConvert == RegexElementLabel.CONCAT &&
|
||||
(origin.Left.EqualityTypeConvert & RegexElementLabel.SINGLE) != 0 &&
|
||||
origin.Left.EqualityTypeConvert == target.Left.EqualityTypeConvert &&
|
||||
origin.Left.Value == target.Left.Value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/**/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegexAnalyzer.IRGenerate"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using StgSharp.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 StgSharp.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,
|
||||
out int minLength
|
||||
)
|
||||
{
|
||||
bool is_operator = !RegexAstNode.IsNullOrEmpty(right) &&
|
||||
(right.Source.Flag & RegexElementLabel.OPERATOR) != 0;
|
||||
|
||||
if (is_operator) // seq to be count is complex
|
||||
{
|
||||
RegexIRGenerator right_ir = GenerateIRRecursion(right, curDepth + 1, 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,
|
||||
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;
|
||||
|
||||
switch (node.Source.Flag)
|
||||
{
|
||||
case RegexElementLabel.GROUP_BEGIN:
|
||||
string group_source = token.Value;
|
||||
|
||||
// process group type
|
||||
bool is_group = group_source.Length == 1; // simple group: (pattern)
|
||||
string group_name =
|
||||
group_source.StartsWith(@"(?<", StringComparison.Ordinal) ?
|
||||
group_source[3..^1] : /* named group (?<name>) */
|
||||
string.Empty; /* unnamed group (?:) */
|
||||
|
||||
RegexIRGenerator groupIR = new();
|
||||
rightIR = GenerateIRRecursion(right, curDepth + 1, 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, out _);
|
||||
minLength = 0;
|
||||
} else if (count_source_span[0] == '+') // at least one
|
||||
{
|
||||
EmitCount(curDepth, count_ir, 1, -1, isGreedy, seq, out length_once);
|
||||
minLength = length_once;
|
||||
} else if (count_source_span[0] == '?') // zero or one
|
||||
{
|
||||
EmitCount(curDepth, count_ir, 0, 1, isGreedy, seq, 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,
|
||||
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,
|
||||
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, 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, 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, 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 StgSharp.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 StgSharp.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 StgSharp.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,105 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegexAnalyzer.SourceGen.CharSetEmitter"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.RegularAnalysis.Text
|
||||
{
|
||||
internal enum CharSetType
|
||||
{
|
||||
|
||||
None,
|
||||
Single,
|
||||
Range,
|
||||
Set
|
||||
|
||||
}
|
||||
|
||||
internal record struct RegexCharSet(
|
||||
string value,
|
||||
CharSetType t,
|
||||
bool accept
|
||||
)
|
||||
{
|
||||
|
||||
public bool IsAcceptable { get; set; } = accept;
|
||||
|
||||
public CharSetType Type { get; set; } = t;
|
||||
|
||||
public string Value { get; set; } = value;
|
||||
|
||||
public override readonly string ToString()
|
||||
{
|
||||
return $"{{value:{value}, type:{Type}, accept:{IsAcceptable}}}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal static partial class RegexAnalyzerBackEnd
|
||||
{
|
||||
|
||||
public static RegexCharSet[] FigureCharSetType(
|
||||
ReadOnlySpan<char> source
|
||||
)
|
||||
{
|
||||
// range or single
|
||||
bool accept;
|
||||
if (source[0] == '[')
|
||||
{
|
||||
int idx;
|
||||
accept = source[1] != '^';
|
||||
idx = accept ? 1 : 2;
|
||||
ReadOnlySpan<char> seq = source.Slice(idx, source.Length - idx - 1);
|
||||
if (seq.IndexOf('-') == -1)
|
||||
{
|
||||
return [new RegexCharSet(seq.ToString(), CharSetType.Single, accept)];
|
||||
} else
|
||||
{
|
||||
List<RegexCharSet> list = [];
|
||||
for (; seq.Length > 0; )
|
||||
{
|
||||
int pos = seq.IndexOf('-');
|
||||
if (pos == -1)
|
||||
{
|
||||
RegexCharSet single = new(seq.ToString(), CharSetType.Single, accept);
|
||||
list.Add(single);
|
||||
break;
|
||||
} else
|
||||
if (pos > 1) {
|
||||
list.Add(new(seq[0..(pos - 1)].ToString(), CharSetType.Single, accept));
|
||||
}
|
||||
list.Add(new($"{seq[pos - 1]}{seq[pos + 1]}", CharSetType.Range, accept));
|
||||
seq = seq[(pos + 2)..];
|
||||
}
|
||||
return [.. list];
|
||||
}
|
||||
} else if (source[0] == '\\')
|
||||
{
|
||||
// set
|
||||
accept = char.IsAsciiLetterUpper(source[0]);
|
||||
return char.ToLower(source[0], CultureInfo.InvariantCulture)
|
||||
switch
|
||||
{
|
||||
's' => [new("s", CharSetType.Set, accept)],
|
||||
'w' => [new("w", CharSetType.Set, accept)],
|
||||
'd' => [new("d", CharSetType.Set, accept)],
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
} else
|
||||
{
|
||||
// unknown, throw
|
||||
throw new InvalidCastException($"Unknown char set: {source}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegexAnalyzer.SourceGen"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
using StgSharp.RegularAnalysis.Abstraction;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.RegularAnalysis.Text
|
||||
{
|
||||
public partial class RegexAnalyzer
|
||||
{
|
||||
|
||||
internal static SequenceEmitter<string> GenerateSource(
|
||||
TextRegexSource source
|
||||
)
|
||||
{
|
||||
SequenceEmitter<string> se = new();
|
||||
List<SequenceEmitter<string>> sub_match_list = [];
|
||||
|
||||
RegexInfo info = source.Info;
|
||||
List<RegexIR> ir = source.IRs;
|
||||
ReadOnlySpan<RegexIR> ir_sp = CollectionsMarshal.AsSpan(ir);
|
||||
RegexIR[] window = new RegexIR[8];
|
||||
Span<RegexIR> scan_win = window;
|
||||
|
||||
_ = se.AppendLine(@"public override MatchResult Match(ReadOnlySpan<char> text)")//
|
||||
.AppendLine(@"{");
|
||||
|
||||
int region_count = info.RegionCount;
|
||||
for (int i = 0; i < region_count; i++)
|
||||
{
|
||||
_ = se.AppendLine($@"int region_{i}_start = -1;")//
|
||||
.AppendLine($@"int region_{i}_end = -1;")
|
||||
.AppendLine($@"bool region_{i}_success;");
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
do
|
||||
{
|
||||
int count = int.Min(8, ir_sp.Length - index);
|
||||
ir_sp.Slice(index, count).CopyTo(scan_win);
|
||||
} while (index < ir.Count);
|
||||
|
||||
foreach (SequenceEmitter<string> _sub in sub_match_list)
|
||||
{
|
||||
_ = se.AppendLine() //
|
||||
.Append(_sub);
|
||||
}
|
||||
|
||||
_ = se.Append(@"}");
|
||||
|
||||
return se;
|
||||
/**/
|
||||
int TryGenerateMatch(
|
||||
Span<RegexIR> scan_win,
|
||||
SequenceEmitter<string> sb
|
||||
)
|
||||
{
|
||||
if (scan_win[0] is not RegexMatchSeqIR seq) {
|
||||
return -1;
|
||||
}
|
||||
_ = sb.Append(string.Empty);
|
||||
return 1;
|
||||
}
|
||||
/**/
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegexAnalyzer"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using StgSharp.RegularAnalysis.Abstraction;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.RegularAnalysis.Text
|
||||
{
|
||||
public record TextRegexSource
|
||||
{
|
||||
|
||||
internal TextRegexSource(
|
||||
string pattern,
|
||||
AbstractSyntaxTree<RegexAstNode, RegexElementLabel> ast,
|
||||
List<RegexIR> base_ir,
|
||||
List<RegexIR> ir,
|
||||
RegexInfo info,
|
||||
Exception? analyseException = null
|
||||
|
||||
)
|
||||
{
|
||||
Pattern = pattern;
|
||||
Ast = ast;
|
||||
IRs = ir;
|
||||
BaseIR = base_ir;
|
||||
AnalyseException = analyseException;
|
||||
Info = info;
|
||||
}
|
||||
|
||||
public Exception? AnalyseException { get; set; }
|
||||
|
||||
public string Pattern { get; }
|
||||
|
||||
internal AbstractSyntaxTree<RegexAstNode, RegexElementLabel> Ast { get; }
|
||||
|
||||
internal List<RegexIR> BaseIR { get; }
|
||||
|
||||
internal List<RegexIR> IRs { get; }
|
||||
|
||||
internal RegexInfo Info { get; }
|
||||
|
||||
}
|
||||
|
||||
public static partial class RegexAnalyzer
|
||||
{
|
||||
|
||||
public static TextRegexSource Analyze(
|
||||
string regex
|
||||
)
|
||||
{
|
||||
#pragma warning disable CA1031
|
||||
// RegexAnalyzer interpreter = new();
|
||||
// interpreter._source = regex;
|
||||
AbstractSyntaxTree<RegexAstNode, RegexElementLabel> tree;
|
||||
RegexInfo info = new();
|
||||
try
|
||||
{
|
||||
tree = GenerateAST(regex);
|
||||
OptimizeTree(tree);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new TextRegexSource(regex, null, null, null, info, ex);
|
||||
}
|
||||
List<RegexIR> base_ir;
|
||||
try
|
||||
{
|
||||
base_ir = GenerateIR(tree, ref info);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new TextRegexSource(regex, tree, null, null, info, ex);
|
||||
}
|
||||
try
|
||||
{
|
||||
ScanAndOptimizeIR(base_ir, out List<RegexIR> opt_ir);
|
||||
List<RegexIR> prefix_ir = EmitPrefix(opt_ir, ref info);
|
||||
return new TextRegexSource(regex, tree, base_ir, prefix_ir, info);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new TextRegexSource(regex, tree, base_ir, null, info, ex);
|
||||
}
|
||||
#pragma warning restore CA1031
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal struct RegexInfo
|
||||
{
|
||||
|
||||
public int RegionCount { get; set; }
|
||||
|
||||
public int MinPredictLength { get; set; }
|
||||
|
||||
public int SingleLineResultCount { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegexAstNode"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using StgSharp.RegularAnalysis.Abstraction;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace StgSharp.RegularAnalysis.Text
|
||||
{
|
||||
internal class RegexAstNode : ISyntaxNode<RegexAstNode, RegexElementLabel>
|
||||
{
|
||||
|
||||
// private RegexAstNode _left, _right;
|
||||
|
||||
public RegexAstNode(
|
||||
Token<RegexElementLabel> source
|
||||
)
|
||||
{
|
||||
Source = source;
|
||||
}
|
||||
|
||||
public int EnumState { get; set; }
|
||||
|
||||
public long NodeFlag => (long)Source.Flag;
|
||||
|
||||
public static RegexAstNode Empty { get; }
|
||||
= new EmptyRegexAstNode();
|
||||
|
||||
public RegexAstNode Previous { get; set; }
|
||||
|
||||
public RegexAstNode Next { get; set; }
|
||||
|
||||
public RegexAstNode Parent { get; set; }
|
||||
|
||||
public RegexAstNode Left
|
||||
{
|
||||
get;
|
||||
set
|
||||
{
|
||||
_ = (field?.Parent = Empty);
|
||||
field = value;
|
||||
if (!IsNullOrEmpty(value)) {
|
||||
value.Parent = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RegexAstNode Right
|
||||
{
|
||||
get;
|
||||
set
|
||||
{
|
||||
_ = (field?.Parent = Empty);
|
||||
field = value;
|
||||
if (!IsNullOrEmpty(value)) {
|
||||
value.Parent = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RegexElementLabel EqualityTypeConvert => Source.Flag;
|
||||
|
||||
public string Value => Source.Value;
|
||||
|
||||
public Token<RegexElementLabel> Source { get; private protected set; }
|
||||
|
||||
public void AppendNode(
|
||||
RegexAstNode nextToken
|
||||
)
|
||||
{
|
||||
Next = nextToken;
|
||||
nextToken.Previous = this;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static (bool Valid, int Value) ComparePrecedence(
|
||||
RegexElementLabel left,
|
||||
RegexElementLabel right
|
||||
)
|
||||
{
|
||||
left &= RegexElementLabel.OPERATOR;
|
||||
right &= RegexElementLabel.OPERATOR;
|
||||
int v_left = (int)left, v_right = (int)right;
|
||||
if (v_left == 0 || v_right == 0) {
|
||||
return (false,0);
|
||||
}
|
||||
return (true,v_left - v_right);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsNullOrEmpty(
|
||||
RegexAstNode node
|
||||
)
|
||||
{
|
||||
return node is null || ReferenceEquals(node, Empty);
|
||||
}
|
||||
/**/
|
||||
|
||||
public void PrependNode(
|
||||
RegexAstNode previousNode
|
||||
)
|
||||
{
|
||||
Previous = previousNode;
|
||||
previousNode.Next = this;
|
||||
}
|
||||
|
||||
public void RemoveChild(
|
||||
RegexAstNode node
|
||||
)
|
||||
{
|
||||
if (Left == node)
|
||||
{
|
||||
Left = Empty;
|
||||
} else if (Right == node) {
|
||||
Right = Empty;
|
||||
}
|
||||
node.Parent = Empty;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static RegexAstNode NewEmptyNode(
|
||||
)
|
||||
{
|
||||
return new EmptyRegexAstNode();
|
||||
}
|
||||
|
||||
internal void RemoveAndAsChild(
|
||||
bool useLeft
|
||||
)
|
||||
{
|
||||
if (useLeft)
|
||||
{
|
||||
Source = Left.Source;
|
||||
Left.Parent = Empty;
|
||||
Left = Empty;
|
||||
} else
|
||||
{
|
||||
Source = Right.Source;
|
||||
Right.Parent = Empty;
|
||||
Right = Empty;
|
||||
}
|
||||
}
|
||||
/**/
|
||||
|
||||
/// <summary>
|
||||
/// Replace the current node with another node, and update the parent and sibling
|
||||
/// references accordingly. This is implemented by copying metadata and nearby
|
||||
/// environment of the given node to the current node, and then update the nearby nodes to
|
||||
/// point to the current node.
|
||||
/// </summary>
|
||||
/// <param name="node">
|
||||
/// The node to replace the current node with.
|
||||
/// </param>
|
||||
internal void ReplaceBy(
|
||||
RegexAstNode node
|
||||
)
|
||||
{
|
||||
Source = node.Source;
|
||||
Left = node.Left;
|
||||
Right = node.Right;
|
||||
if (!IsNullOrEmpty(Left)) {
|
||||
Left.Parent = this;
|
||||
}
|
||||
if (!IsNullOrEmpty(Right)) {
|
||||
Right.Parent = this;
|
||||
}
|
||||
Previous = node.Previous;
|
||||
Next = node.Next;
|
||||
|
||||
RegexAstNode prev = Previous;
|
||||
RegexAstNode next = Next;
|
||||
if (!IsNullOrEmpty(prev)) {
|
||||
prev.Next = this;
|
||||
}
|
||||
if (!IsNullOrEmpty(next)) {
|
||||
next.Previous = this;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EmptyRegexAstNode : RegexAstNode
|
||||
|
||||
{
|
||||
|
||||
public EmptyRegexAstNode() : base(Token<RegexElementLabel>.Empty) { }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegexElementLabel"
|
||||
// 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 StgSharp.RegularAnalysis.Text
|
||||
{
|
||||
[Flags]
|
||||
public enum RegexElementLabel
|
||||
{
|
||||
|
||||
NONE = 0,
|
||||
UNIT = 1,
|
||||
UNIT_SET = 2,
|
||||
UNIT_SPAN = 4,
|
||||
GROUP_BEGIN = 8,
|
||||
GROUP_END = 16,
|
||||
COUNT = 32,
|
||||
CONCAT = 64,
|
||||
ALT = 128,
|
||||
SEQUENCE = UNIT | UNIT_SPAN | UNIT_SET,
|
||||
SINGLE = UNIT | UNIT_SET,
|
||||
OPERATOR = COUNT | ALT | CONCAT,
|
||||
VAST_OPERATOR = OPERATOR | GROUP_BEGIN,
|
||||
ATOM_BEGIN = SEQUENCE | GROUP_BEGIN ,
|
||||
ATOM_END = SEQUENCE | GROUP_END | COUNT,
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 StgSharp.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 StgSharp.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,220 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// 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 StgSharp.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)}\"";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for a certain string pattern or charset from current position.
|
||||
/// </summary>
|
||||
internal sealed class RegexMatchSeqIR(string pattern) : RegexIR(RegexIRCommand.Match_Seq), IRegexSequence
|
||||
{
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
public bool IsFullyOptimized { get; set; }
|
||||
|
||||
public int CommandCount { get; set; } = count;
|
||||
|
||||
protected override string AsCommand()
|
||||
{
|
||||
return $"TRY {CommandCount}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 StgSharp.RegularAnalysis.Abstraction;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.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,69 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegexTokenParser"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using StgSharp.RegularAnalysis.Abstraction;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.IO.Pipes;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.RegularAnalysis.Text
|
||||
{
|
||||
internal class RegexTokenParser : TokenParser<RegexElementLabel, RegexElementLabel>
|
||||
{
|
||||
|
||||
private readonly Queue<Token<RegexElementLabel>> _buffer = [];
|
||||
|
||||
private RegexElementLabel _prev;
|
||||
|
||||
public override bool IsEmpty => Source.IsEmpty && _buffer.Count == 0;
|
||||
|
||||
public override Token<RegexElementLabel> ReadToken()
|
||||
{
|
||||
if (_buffer.TryDequeue(out Token<RegexElementLabel> token)) {
|
||||
return token;
|
||||
}
|
||||
if (Source.TryReadToken(out Token<RegexElementLabel> orig))
|
||||
{
|
||||
if (((int)(orig.Flag & RegexElementLabel.ATOM_BEGIN) != 0) && ((int)_prev != 0))
|
||||
{
|
||||
Token<RegexElementLabel> ret = new Token<RegexElementLabel>(string.Empty,
|
||||
orig.Line,
|
||||
orig.Column,
|
||||
RegexElementLabel.CONCAT);
|
||||
_buffer.Enqueue(orig);
|
||||
_prev = orig.Flag & RegexElementLabel.ATOM_END;
|
||||
return ret;
|
||||
} else
|
||||
{
|
||||
_prev = orig.Flag & RegexElementLabel.ATOM_END;
|
||||
return orig;
|
||||
}
|
||||
} else
|
||||
{
|
||||
return Token<RegexElementLabel>.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool TryReadToken(
|
||||
out Token<RegexElementLabel> t
|
||||
)
|
||||
{
|
||||
if (IsEmpty)
|
||||
{
|
||||
t = Token<RegexElementLabel>.Empty;
|
||||
return false;
|
||||
}
|
||||
t = ReadToken();
|
||||
return t != Token<RegexElementLabel>.Empty;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegexTokenReader"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using StgSharp.RegularAnalysis.Abstraction;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Permissions;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.RegularAnalysis.Text
|
||||
{
|
||||
internal class RegexTokenReader : ITokenReader<RegexElementLabel>
|
||||
{
|
||||
|
||||
private static string _source;
|
||||
|
||||
private int position = 0;
|
||||
|
||||
public RegexTokenReader() { }
|
||||
|
||||
public RegexTokenReader(string source)
|
||||
{
|
||||
_source = source;
|
||||
}
|
||||
|
||||
public bool IsEmpty => string.IsNullOrEmpty(_source) || position >= _source.Length;
|
||||
|
||||
public Token<RegexElementLabel> ReadToken()
|
||||
{
|
||||
int pos = position;
|
||||
switch (_source[pos])
|
||||
{
|
||||
case '\\':
|
||||
if (pos + 1 == _source.Length) {
|
||||
throw new InvalidOperationException("Unclosed char");
|
||||
}
|
||||
if (_source[pos + 1] == 'x')
|
||||
{
|
||||
string value = _source[(pos + 1)..(pos + 3)];
|
||||
pos += 4;
|
||||
if (uint.TryParse(value, out uint code) && Rune.TryCreate(code, out Rune result))
|
||||
{
|
||||
return new Token<RegexElementLabel>(result.ToString(), 0, pos, RegexElementLabel.UNIT);
|
||||
} else
|
||||
{
|
||||
throw new InvalidCastException($"Failed to convert {value} to a valid ascii code point.");
|
||||
}
|
||||
} else if (_source[pos + 1] == 'u')
|
||||
{
|
||||
string value = _source[(pos + 1)..(pos + 5)];
|
||||
pos += 6;
|
||||
if (uint.TryParse(value, out uint code) && Rune.TryCreate(code, out Rune result))
|
||||
{
|
||||
return new Token<RegexElementLabel>(result.ToString(), 0, pos, RegexElementLabel.UNIT);
|
||||
} else
|
||||
{
|
||||
throw new InvalidCastException($"Failed to convert {value} to a valid unicode code point.");
|
||||
}
|
||||
} else if (_source[pos + 1] == 'p')
|
||||
{
|
||||
int i = pos + 2;
|
||||
if (_source[i] != '{') {
|
||||
throw new InvalidOperationException("Invalid Unicode property escape sequence.");
|
||||
}
|
||||
for (; i < _source.Length; i++)
|
||||
{
|
||||
if (_source[i] == '}')
|
||||
{
|
||||
position = i + 1;
|
||||
return new Token<RegexElementLabel>(_source[(pos)..(i + 1)], 0, pos,
|
||||
RegexElementLabel.UNIT_SET);
|
||||
}
|
||||
}
|
||||
throw new InvalidOperationException("Unclosed Unicode property escape sequence.");
|
||||
} else if (_source[pos + 1] == '.')
|
||||
{
|
||||
position = pos + 2;
|
||||
return new Token<RegexElementLabel>(".", 0, pos, RegexElementLabel.UNIT);
|
||||
} else
|
||||
{
|
||||
position = pos + 2;
|
||||
return new Token<RegexElementLabel>(_source[(pos)..(pos + 2)], 0, pos,
|
||||
RegexElementLabel.UNIT_SET);
|
||||
}
|
||||
case '[':
|
||||
for (int i = pos + 1; i < _source.Length; i++)
|
||||
{
|
||||
if (_source[i] == ']' && _source[i - 1] != '\\')
|
||||
{
|
||||
int length = (i - pos - 1);
|
||||
position = i + 1;
|
||||
return new Token<RegexElementLabel>(_source[(pos)..(i + 1)], 0, position,
|
||||
RegexElementLabel.UNIT_SET);
|
||||
}
|
||||
}
|
||||
throw new InvalidOperationException("Unclosed character set");
|
||||
case '|':
|
||||
position = pos + 1;
|
||||
return new Token<RegexElementLabel>(_source[(pos)..(pos + 1)], 0, pos, RegexElementLabel.ALT);
|
||||
case '(':
|
||||
|
||||
// very comlex here
|
||||
return ReadGroupBeginToken();
|
||||
case ')':
|
||||
position = pos + 1;
|
||||
return new Token<RegexElementLabel>(_source[(pos)..(pos + 1)], 0, pos, RegexElementLabel.GROUP_END);
|
||||
case '{':
|
||||
for (int i = pos + 1; i < _source.Length; i++)
|
||||
{
|
||||
if (_source[i] == '}')
|
||||
{
|
||||
position = i + 1;
|
||||
return new Token<RegexElementLabel>(_source[(pos)..(i + 1)], 0, pos,
|
||||
RegexElementLabel.COUNT);
|
||||
}
|
||||
}
|
||||
throw new InvalidOperationException("Unclosed count specifier");
|
||||
case '*' or '+':
|
||||
int count = pos + 1 >= _source.Length ? 1 : (_source[pos + 1] == '?' ? 2 : 1);
|
||||
position = pos + count;
|
||||
return new Token<RegexElementLabel>(_source[(pos)..(pos + count)], 0, pos, RegexElementLabel.COUNT);
|
||||
case '?':
|
||||
position = pos + 1;
|
||||
return new Token<RegexElementLabel>(_source[(pos)..(pos + 1)], 0, pos, RegexElementLabel.COUNT);
|
||||
case '.':
|
||||
position = pos + 1;
|
||||
return new Token<RegexElementLabel>(_source[(pos)..(pos + 1)], 0, pos, RegexElementLabel.UNIT_SET);
|
||||
default:
|
||||
position = pos + 1;
|
||||
return new Token<RegexElementLabel>(_source[(pos)..(pos + 1)], 0, pos, RegexElementLabel.UNIT);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryReadToken(out Token<RegexElementLabel> t)
|
||||
{
|
||||
if (IsEmpty)
|
||||
{
|
||||
t = Token<RegexElementLabel>.Empty;
|
||||
return false;
|
||||
}
|
||||
t = ReadToken();
|
||||
return Token<RegexElementLabel>.Empty != t;
|
||||
}
|
||||
|
||||
private Token<RegexElementLabel> ReadGroupBeginToken()
|
||||
{
|
||||
if (IsEmpty) {
|
||||
throw new InvalidOperationException("Source is null.");
|
||||
}
|
||||
|
||||
int start = position;
|
||||
|
||||
if (start >= _source.Length || _source[start] != '(') {
|
||||
throw new InvalidOperationException("ReadGroupBeginToken called when current char is not '('.");
|
||||
}
|
||||
|
||||
// Case 1: plain capturing group "("
|
||||
if (start + 1 >= _source.Length || _source[start + 1] != '?')
|
||||
{
|
||||
position = start + 1;
|
||||
return new Token<RegexElementLabel>("(", 0, start, RegexElementLabel.GROUP_BEGIN);
|
||||
}
|
||||
|
||||
// From here we have "(?"
|
||||
if (start + 2 >= _source.Length)
|
||||
{
|
||||
throw new InvalidOperationException("Unfinished group prefix '(?'.");
|
||||
}
|
||||
|
||||
char c2 = _source[start + 2];
|
||||
|
||||
// Case 2: non-capturing group "(?:"
|
||||
if (c2 == ':')
|
||||
{
|
||||
position = start + 3;
|
||||
return new Token<RegexElementLabel>("(?:", 0, start, RegexElementLabel.GROUP_BEGIN);
|
||||
}
|
||||
|
||||
// Assertions (write the first-level cases, but not supported yet)
|
||||
if (c2 == '=')
|
||||
{
|
||||
// "(?="
|
||||
position = start + 3;
|
||||
throw new NotSupportedException("Positive lookahead '(?=...)' is not supported yet.");
|
||||
}
|
||||
if (c2 == '!')
|
||||
{
|
||||
// "(?!"
|
||||
position = start + 3;
|
||||
throw new NotSupportedException("Negative lookahead '(?!...)' is not supported yet.");
|
||||
}
|
||||
|
||||
// Named capturing group or lookbehind starts with "(?<"
|
||||
if (c2 == '<')
|
||||
{
|
||||
if (start + 3 >= _source.Length) {
|
||||
throw new InvalidOperationException("Unfinished group prefix '(?<'.");
|
||||
}
|
||||
|
||||
char c3 = _source[start + 3];
|
||||
|
||||
// Look behind prefixes: "(?<=" and "(?<!"
|
||||
if (c3 == '=')
|
||||
{
|
||||
position = start + 4;
|
||||
throw new NotSupportedException("Positive look behind '(?<=...)' is not supported yet.");
|
||||
}
|
||||
if (c3 == '!')
|
||||
{
|
||||
position = start + 4;
|
||||
throw new NotSupportedException("Negative look behind '(?<!...)' is not supported yet.");
|
||||
}
|
||||
|
||||
// Otherwise: "(?<name>"
|
||||
int nameStart = start + 3;
|
||||
|
||||
// Read name until '>'
|
||||
int i = nameStart;
|
||||
|
||||
// Name must start with [A-Za-z_]
|
||||
if (i >= _source.Length)
|
||||
{
|
||||
throw new InvalidOperationException("Unfinished named group prefix.");
|
||||
}
|
||||
|
||||
char first = _source[i];
|
||||
if (!(first == '_' || char.IsLetter(first))) {
|
||||
throw new InvalidOperationException($"Invalid named group name start '{first}' at position {i}.");
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
while (i < _source.Length)
|
||||
{
|
||||
char ch = _source[i];
|
||||
if (ch == '>')
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(ch == '_' || char.IsLetterOrDigit(ch))) {
|
||||
throw new InvalidOperationException($"Invalid character '{ch}' in group name at position {i}.");
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i >= _source.Length || _source[i] != '>') {
|
||||
throw new InvalidOperationException("Unclosed named group prefix; missing '>'.");
|
||||
}
|
||||
|
||||
// Consume "(?<name>"
|
||||
int consumedLen = i - start + 1; // include '>'
|
||||
string text = _source.Substring(start, consumedLen);
|
||||
position = start + consumedLen;
|
||||
|
||||
return new Token<RegexElementLabel>(text, 0, start, RegexElementLabel.GROUP_BEGIN);
|
||||
}
|
||||
|
||||
// Optional: comments "(?#...)" or inline options "(?i:...)" etc.
|
||||
// First-level cases listed but not supported yet:
|
||||
if (c2 == '#')
|
||||
{
|
||||
position = start + 3;
|
||||
throw new NotSupportedException("Comment group '(?#...)' is not supported yet.");
|
||||
}
|
||||
|
||||
// Inline options: (?i:...), (?m:...), (?-i:...), (?im), etc.
|
||||
// Too many dialect differences; reserve the prefix but mark unsupported.
|
||||
if (char.IsLetter(c2) || c2 == '-')
|
||||
{
|
||||
// We don't know how long the option spec is here; do NOT advance position too much.
|
||||
// Keep position at '(' so caller can decide how to handle, or just throw.
|
||||
position = start;
|
||||
throw new NotSupportedException("Inline option groups like '(?i:...)' are not supported yet.");
|
||||
}
|
||||
|
||||
// Unknown "(?X" prefix
|
||||
position = start;
|
||||
throw new InvalidOperationException($"Unknown or unsupported group prefix starting at position {start}: '{_source.Substring(start, Math.Min(4, _source.Length - start))}'");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="RegexTokenUtil"
|
||||
// Project: StgSharp
|
||||
// Copyright (c) Nitload.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using StgSharp.RegularAnalysis.Abstraction;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.RegularAnalysis.Text
|
||||
{
|
||||
internal static class RegexTokenUtil
|
||||
{
|
||||
|
||||
public static bool IsAccurateChar(
|
||||
this Token<RegexElementLabel> token
|
||||
)
|
||||
{
|
||||
return ((token.Flag & RegexElementLabel.UNIT) != 0 && token.Value.Length == 1) ||
|
||||
(token.Flag & RegexElementLabel.UNIT_SPAN) != 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// file="TextRegex"
|
||||
// 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 StgSharp.RegularAnalysis.Text
|
||||
{
|
||||
public abstract class TextRegex
|
||||
{
|
||||
|
||||
public abstract MatchResult Match(
|
||||
ReadOnlySpan<char> text
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public ref struct MatchResult { }
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
### basic
|
||||
char/charset->char/charset
|
||||
|
||||
### count
|
||||
char/charset-match->count increment
|
||||
count increment-loop back->char/charset
|
||||
char/charset-miss match->count end
|
||||
count end-exit->count increment-judge->next node
|
||||
|
||||
### match group/lookaround
|
||||
|
||||
left boarder-> match group start //push stack, add group index
|
||||
-> match logic // recored index
|
||||
-> match group end //pop stack, set group end, build sub sequence
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user