// ----------------------------------------------------------------------------- // 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 enumerator = new(root); List 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 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(str, left.Source.Line, left.Source.Column, str.Length == 1 ? RegexElementLabel.UNIT : RegexElementLabel.UNIT_SPAN)); return true; } private static void OptimizeTree( AbstractSyntaxTree tree, ref RegexInfo info ) { // phase0: scan container and remove no parent nodes _ = tree.AllNodes.RemoveWhere(node => node.Parent == null && node != tree.Root); TreeEnumerator enumerator = new(tree.Root); RegexAstNode current; // phase1: ALT flatten // phase2: CONCAT normalize int i = 0; List 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 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(); } } } 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; } } } 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 /// /// Get union beginning part of cases /// /// /// /// /// /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private static RegexAstNode MergeAlt( List 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(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 c ) : base(c[0].Source) { Cases = c; Level = level; } public List 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 buffer = []; for (int i = 0; i < Cases.Count; i++) { if ((Cases[i].EqualityTypeConvert & RegexElementLabel.SEQUENCE) != 0) { RegexAstNode concat = new(new Token(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(string.Empty, 0, Source.Column, RegexElementLabel.ALT)); UnionBegin = branch; UnionEnd = branch; } else { branch = new RegexAstNode(new Token(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(_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 /**/ } }