﻿B4J=true
Group=Default Group
ModulesStructureVersion=1
Type=Class
Version=9.3
@EndOfDesignText@
'RegexConstructor class Version 2
'Change from 1: corrects the NodeClass value when value is a string.
'Aims to replace the whole Regex functionality without the gibberish.
'By Steve Philipp, Heuristx on the B4X forum.
'The Literal function was copied from Erel's RegexBuilder with one change, it returns an empty string if the input is empty.

Sub Class_Globals
	
	Type CharacterClassType(Value As Int, Description As String, RegexValue As String)
	Type CharacterClassValue(CharacterType As CharacterClassType, Count As Int)
	
	Public ccAny As CharacterClassType
	Public ccTab As CharacterClassType
	Public ccQuote As CharacterClassType

	Public ccAlpha As CharacterClassType
	
	Public ccAlphaNum As CharacterClassType
	Public ccNotAlphaNum As CharacterClassType

	Public ccDigit As CharacterClassType
	Public ccNonDigit As CharacterClassType

	Public ccSpace As CharacterClassType
	Public ccNonSpace As CharacterClassType
	
	Public ccCR As CharacterClassType
	Public ccCRLF As CharacterClassType
	Public ccNewLine As CharacterClassType
	Public ccAnyNewLine As CharacterClassType
		
	Public ucUpper As CharacterClassType
	Public ucLower As CharacterClassType
	Public ucLetter As CharacterClassType
	Public ucPunct As CharacterClassType
	Public ucControl As CharacterClassType
	Public ucNumber As CharacterClassType
	Public ucOpeningBrackets As CharacterClassType
	Public ucClosingBrackets As CharacterClassType
	Public ucSymbol As CharacterClassType
	Public ucMathSymbol As CharacterClassType
	Public ucCurrency As CharacterClassType

	Public SymCopyright As CharacterClassType
	Public SymTradeMark As CharacterClassType
	Public SymYen As CharacterClassType
	Public SymPound As CharacterClassType
	Public SymCent As CharacterClassType
	Public SymDegree As CharacterClassType
	Public SymPlusMinus As CharacterClassType
	Public SymSquare As CharacterClassType
	Public SymMu As CharacterClassType
	Public SymQuarter As CharacterClassType
	Public SymHalf As CharacterClassType
	Public SymThreeQuarters As CharacterClassType

  Type NamedRangeType(Value As Int, Description As String, RegexValue As String)	
	Public RngAbcLower As NamedRangeType
	Public RngAbcUpper As NamedRangeType
	Public RngAbcLowerUpper As NamedRangeType
	Public RngDigits As NamedRangeType
	Public RngAlpahnumeric As NamedRangeType
	Public RngLowerAlpahnumeric As NamedRangeType
	Public RngUpperAlpahnumeric As NamedRangeType
	Public RngSentenceEndMarkers As NamedRangeType
	Public RngWord As NamedRangeType
	Public RngPunctuation As NamedRangeType
	Public RngHex As NamedRangeType
	Public RngBinary As NamedRangeType
	Public RngVowels As NamedRangeType
	Public RngConsonants As NamedRangeType
		
	Public Const QtyAny As Int = -1
	Public Const QtyKeep As Int = InvalidInt
	Type QtyBehavior(Value As Int, Description As String, RegexValue As String)
	Type TQty(Minimum As Int, Maximum As Int)
	Public QtyShort As QtyBehavior
	Public QtyLong As QtyBehavior
	Public QtyShort As QtyBehavior
	Public QtyNoRetry As QtyBehavior
	Public QtyKeepBehavior As QtyBehavior
	Public QtyUndefinedBehavior As QtyBehavior
	
	Type GroupType(Value As Int, Description As String, RegexValue As String)
	Public GrpRegular As GroupType
	Public GrpBacktrackOver As GroupType
	Public GrpPositiveLookAhead As GroupType
	Public GrpNegativeLookAhead As GroupType
	Public GrpPositiveLookBehind As GroupType
	Public GrpNegativeLookBehind As GroupType
	Public GrpCountedLookBehind As GroupType
	Public GrpNotCapturing As GroupType

	Type BoundaryType(Value As Int, Description As String, RegexValue As String)
	Public BndWord As BoundaryType
	Public BndNotWord As BoundaryType
	Public BndPrevMatch As BoundaryType
	
	'Type PatternType(Pattern As String)
	Type PatternValue(Pattern As String)
	
	Private Const VocAnToAbsStart As Int = 1
	Private Const VocAnToOptStart As Int = 2
	Private Const VocAnToAbsEnd As Int = 3
	Private Const VocAnToOptEnd As Int = 4
		
	Private Const VocSetStart As Int = 5
	Private Const VocSetEnd As Int = 6
	Private Const VocSetSeparator As Int = 7
	Private Const VocSetNegate As Int = 8
	Private Const VocIntersectingSetStart As Int = 9
	
	Private Const VocQtyAny As Int = 10
	Private Const VocQtyOptional As Int = 11
	Private Const VocQtyAtLEastOne As Int = 12
	Private Const VocQtyStart As Int = 13
	Private Const VocQtySeparator As Int = 14
	Private Const VocQtyEnd As Int = 15
	Private Const VocQtyBehavior As Int = 16
	
	Private Const VocGrpStart As Int = 17
	Private Const VocGrpEnd As Int = 18

	Private Const VocAltOr As Int = 19

	Private Metacharacters As String 'Assigned with the Regex vocabuulary so we keep everything in one place.
	Private MetacharactersInSets As String 'Assigned with the Regex vocabuulary so we keep everything in one place.
	  
	Private FIgnoreCase As Int
	Private FMultiline As Int
	Private FAnchorToStart As Boolean
	Private FAnchorToEnd As Boolean

  Type TreeNode(Name As String, Value As Object, Contents As List, Parent As TreeNode)	
	
	'When introducing a new type, also update: Nodes\NodeClass, Visualization\NodeValueToString, Translation\NodeRegexPattern, Nodes\CopyNodeValue	
	Type CharacterClass(Value As CharacterClassValue)
	Type Range(First As String, Last As String)
	Type NamedRange(Value As NamedRangeType, Exclude As Boolean)
	Type Set(Exclude As Boolean)
	Type IntersectingSet(Exclude As Boolean) 'Exclude is a dummy, can't have an empty Type. All intersecting ranges exclude.
	Type Counter(Minimum As Int, Maximum As Int, Behavior As QtyBehavior)
	Type Group(GroupType As GroupType, Minimum As Int, Maximum As Int)
	Type Boundary(BoundaryType As BoundaryType)
	Type OrClause(Single As Boolean)
	Type Pattern(Pattern As PatternValue)
	Private Const clsText As String = "Text"
	'Private Const clsCharacterClass As String = "CharacterClass"
	Private Const clsRange As String = "Range"
	Private Const clsNamedRange As String = "NamedRange"
	Private Const clsSet As String = "Set"
	Private Const clsIntersectingSet As String = "IntersectingSet"
	Private Const clsCounter As String = "Counter"
	Private Const clsGroup As String = "Group"
	Private Const clsBoundary As String = "Boundary"
	Private Const clsOrClause As String = "OrClause"
	'Private Const clsPattern As String = "Pattern"

	Private GroupNodes As List
	Private SetNodes As List
	
	Private RegexVocabularyMap As Map
	
	Private InvalidNode As TreeNode
	Private InvalidInt As Int = -2147483648
	Private InvalidName As String = InvalidInt
	
	Private Tree As TreeNode	
  Private FlatList As List	
	Private NodeStack As List
	
	Private Template As StringBuilder
	
	Private NameBuffer As String = ""
	
End Sub

#Region Initialize

Public Sub Initialize(IgnoreCase As Boolean, SearchLineByLine As Boolean, MustMatchAtStart As Boolean, MustEndWithMatch As Boolean) As RegexConstructor
	InitializeRegexVocabularyMap
	InitializeParameters	
	InvalidNode = CreateNode(InvalidName)
	Return Reset(IgnoreCase, SearchLineByLine, MustMatchAtStart, MustEndWithMatch)
End Sub

Private Sub InitializeRegexVocabularyMap
	Metacharacters = "[\^$.|?*+()"
	MetacharactersInSets = "\^-]" 'The order is important, backslash must be the first character!
	
	RegexVocabularyMap.Initialize
	RegexVocabularyMap.Put(VocAnToAbsStart, "\A")
	RegexVocabularyMap.Put(VocAnToOptStart, "^")
	RegexVocabularyMap.Put(VocAnToAbsEnd, "\Z")
	RegexVocabularyMap.Put(VocAnToOptEnd, "$")
		
	RegexVocabularyMap.Put(VocSetStart, "[")
	RegexVocabularyMap.Put(VocSetEnd, "]")
	RegexVocabularyMap.Put(VocSetSeparator, "-")
	RegexVocabularyMap.Put(VocSetNegate, "^")
	RegexVocabularyMap.Put(VocIntersectingSetStart, "&&[^")
	
	RegexVocabularyMap.Put(VocQtyAny, "*")
	RegexVocabularyMap.Put(VocQtyOptional, "?")
	RegexVocabularyMap.Put(VocQtyAtLEastOne, "+")
	RegexVocabularyMap.Put(VocQtyStart, "{")
	RegexVocabularyMap.Put(VocQtySeparator, ",")
	RegexVocabularyMap.Put(VocQtyEnd, "}")
	RegexVocabularyMap.Put(VocQtyBehavior, "?")
	
	RegexVocabularyMap.Put(VocGrpStart, "(")
	RegexVocabularyMap.Put(VocGrpEnd, ")")

	RegexVocabularyMap.Put(VocAltOr, "|")

End Sub

Private Sub InitializeParameters
	
	GroupNodes.Initialize2(Array As String(clsCounter, clsGroup, clsBoundary, clsOrClause))
	SetNodes.Initialize2(Array As String(clsRange, clsNamedRange, clsSet, clsIntersectingSet))	

	BndWord = CreateBoundaryType(1, "Match at word boundary", "\b")
	BndNotWord = CreateBoundaryType(2, "Match not word boundary", "\B")
	BndPrevMatch = CreateBoundaryType(3, "Previous match End", "\G")
	
	QtyShort = CreateQtyBehavior(2, "Stop at first chance", "?")
	QtyLong = CreateQtyBehavior(1, "Repeat till last match", "")
	QtyShort = CreateQtyBehavior(3, "Stop at first chance, no backtracking/retries", "?")
	QtyNoRetry= CreateQtyBehavior(4, "Repeat till last match, no backtracking/retries", "+")
	QtyKeepBehavior = CreateQtyBehavior(5, "Keep original value", "")
	QtyUndefinedBehavior = CreateQtyBehavior(6, "Undefined", "")

	GrpRegular = CreateGroupType(0, "Simple group", "")
	GrpBacktrackOver = CreateGroupType(1, "Backtracking will pass over", "?>")
	GrpPositiveLookAhead = CreateGroupType(2, "Positive lookahead", "?=")
	GrpNegativeLookAhead = CreateGroupType(3, "Negative lookahead", "?!")
	GrpPositiveLookBehind = CreateGroupType(4, "Positive lookbehind", "?<=")
	GrpNegativeLookBehind = CreateGroupType(5, "Negative lookbehind", "?<!")
	GrpCountedLookBehind = CreateGroupType(6, "Counted lookbehind", "?<=")
	GrpNotCapturing = CreateGroupType(7, "Group will not record", "?:")

	RngAbcLower = CreateNamedRangeType(1, "AbcLower", "a-z")
	RngAbcUpper = CreateNamedRangeType(2, "AbcUpper", "A-Z")
	RngAbcLowerUpper = CreateNamedRangeType(3, "AbcLowerUpper", "A-Za-z")
	RngDigits = CreateNamedRangeType(4, "Digits", "0-9")
	RngAlpahnumeric = CreateNamedRangeType(5, "Alphanum", "A-Za_z0-9")
	RngLowerAlpahnumeric = CreateNamedRangeType(6, "LowerAlphanum", "a_z0-9")
	RngUpperAlpahnumeric = CreateNamedRangeType(7, "UpperAlphaNum", "A_Z0-9")
	RngSentenceEndMarkers = CreateNamedRangeType(8, "SentenceEndMarkers", ".?!")
	RngWord = CreateNamedRangeType(9, "Word", $"${RngAbcLowerUpper}-'"$)
	RngPunctuation = CreateNamedRangeType(10,"Punctuation", $"!`()-;:'",./?"$)
	RngHex = CreateNamedRangeType(11, "Hex digits", "A-Fa-f0-9")
	RngBinary = CreateNamedRangeType(12, "Binary digits", "0-1")
	RngVowels = CreateNamedRangeType(13, "Vowels", "aeiouAEIOU")
	RngConsonants = CreateNamedRangeType(14, "Consonants", "[A-Za-z&&[^AEIOUaeiou]]")

	ccAny = CreateCharacterClassType(1, $"The "any" character"$, ".")
	ccTab = CreateCharacterClassType(2, "Tab"$, "\t")
	ccQuote = CreateCharacterClassType(3, "Quote"$, $"\""$)
	ccAlphaNum = CreateCharacterClassType(4, "Alphanumeric", "\w")
	ccNotAlphaNum = CreateCharacterClassType(5, "Not alphanumeric", "\W")
	ccDigit = CreateCharacterClassType(6, "Digit", "\d")
	ccNonDigit = CreateCharacterClassType(7, "Not digit", "\D")
	ccSpace = CreateCharacterClassType(8, "Space", "\s")
	ccNonSpace = CreateCharacterClassType(9, "Not space", "\S")
	
	ccCR = CreateCharacterClassType(10, "CR", "\r")
	ccCRLF = CreateCharacterClassType(11, "CRLF", "\r\n")
	ccNewLine = CreateCharacterClassType(12, "NL", "\n")
	ccAnyNewLine = CreateCharacterClassType(13, "AnyNewLine", "\R")
		
	ucUpper = CreateCharacterClassType(14, "uUpper", "\p{Lu}")
	ucLower = CreateCharacterClassType(15, "uLower", "\p{Ll}")
	ucLetter = CreateCharacterClassType(16, "uLetter", "\p{L}")
	ucPunct = CreateCharacterClassType(17, "uPunct", "\p{P}")
	ucControl = CreateCharacterClassType(18, "uCtrl", "\p{C}")
	ucNumber = CreateCharacterClassType(19, "uNum", "\p{N}")
	ucOpeningBrackets = CreateCharacterClassType(20, "uOpeningBrackets", "\p{Ps}")
	ucClosingBrackets = CreateCharacterClassType(21, "uClosingBrackets", "\p{Pe}")
	ucSymbol = CreateCharacterClassType(22, "uSym", "\p{S}") '+, -
	ucMathSymbol = CreateCharacterClassType(23, "uMath", "\p{Sm}")
	ucCurrency = CreateCharacterClassType(24, "uCurrency", "\p{Sc}")

	SymCopyright = CreateCharacterClassType(25, "symCopyright", "©")
	SymTradeMark = CreateCharacterClassType(26, "symTrademark", "®")
	SymYen = CreateCharacterClassType(27, "symYen", "¥")
	SymPound = CreateCharacterClassType(28, "symPound", "£")
	SymCent = CreateCharacterClassType(29, "symCent", "¢")
	SymDegree = CreateCharacterClassType(30, "symDegree", "°")
	SymPlusMinus = CreateCharacterClassType(31, "symPlusMinus", "±")
	SymSquare = CreateCharacterClassType(32, "symSquare", "²")
	SymMu = CreateCharacterClassType(33, "symMu", "µ")
	SymQuarter = CreateCharacterClassType(34, "symQuarter", "¼")
	SymHalf = CreateCharacterClassType(35, "symHalf", "½")
	SymThreeQuarters = CreateCharacterClassType(36, "symThreeQuarters", "¾")
	
End Sub

Private Sub CreateBoundaryType(Value As Int, Description As String, RegexValue As String) As BoundaryType
  Dim Result As BoundaryType
	Result.Initialize
	Result.Value = Value
	Result.Description = Description
	Result.RegexValue = RegexValue
	Return Result
End Sub

Private Sub CreateQtyBehavior(Value As Int, Description As String, RegexValue As String) As QtyBehavior
	Dim Result As QtyBehavior
	Result.Initialize
	Result.Value = Value
	Result.Description = Description
	Result.RegexValue = RegexValue
	Return Result
End Sub

Private Sub CreateGroupType(Value As Int, Description As String, RegexValue As String) As GroupType
  Dim Result As GroupType
	Result.Initialize
	Result.Value = Value
	Result.Description = Description
	Result.RegexValue = RegexValue
	Return Result
End Sub

Private Sub CreateCharacterClassType(Value As Int, Description As String, RegexValue As String) As CharacterClassType
	Dim Result As CharacterClassType
	Result.Initialize
	Result.Value = Value
	Result.Description = Description
	Result.RegexValue = RegexValue
	Return Result
End Sub

Private Sub CreateCharacterClassValue(ClassType As CharacterClassType, Count As Int) As CharacterClassValue
	Dim Result As CharacterClassValue
	Result.Initialize
	Result.CharacterType = ClassType
	Result.Count = Count
	Return Result
End Sub

Private Sub CreateNamedRangeType(Value As Int, Description As String, RegexValue As String) As NamedRangeType
	Dim Result As NamedRangeType
	Result.Initialize
	Result.Value = Value
	Result.Description = Description
	Result.RegexValue = RegexValue
	Return Result
End Sub

Public Sub Reset(IgnoreCase As Boolean, SearchLineByLine As Boolean, MustMatchAtStart As Boolean, MustEndWithMatch As Boolean) As RegexConstructor
	FIgnoreCase = IIf(IgnoreCase, Regex.CASE_INSENSITIVE, 0)
	FMultiline = IIf(SearchLineByLine, Regex.MULTILINE, 0)
	FAnchorToStart = MustMatchAtStart
	FAnchorToEnd = MustEndWithMatch
	Template.Initialize
  Return Clear	
End Sub

Public Sub Clear As RegexConstructor
	NameBuffer = "Tree"
	Tree = CreateNode(Null)
	Tree.Value = Tree
	FlatList.Initialize
	NodeStack.Initialize
	Template.Initialize
	PushNode(Tree)
	Return Me
End Sub

#End Region

#Region Aux

Private Sub CleanType(V As Object) As String
	Dim Result As String = GetType(V)
	Dim p As Int = Result.IndexOf("$_")
	If p > -1 Then Result = Result.SubString(p + 2)
	p = Result.LastIndexOf(".")
	If P > -1 Then Result = Result.SubString(p + 1)	
	Return Result.CharAt(0).As(String).ToUpperCase & Result.SubString(1)
End Sub

Private Sub LstCaselessIndexOf(lst As List, Item As String) As Int
	For i = 0 To lst.Size - 1
		If Item.EqualsIgnoreCase(lst.Get(i)) Then Return i
	Next
	Return -1
End Sub

'Returns a string of multiple strings repeated in it.
Private Sub Multiple(s As String, Count As Int) As String
	If s.Length = 0 Then Return ""
	Dim sb As StringBuilder
	sb.Initialize
	For i = 1 To Count
		sb.Append(s)
	Next
	Return sb.ToString
End Sub

#End Region

#Region Visualization

Private Sub NodeValueToString(N As TreeNode) As String
	Dim V As Object = N.Value
	If V Is String Then Return V
	If V Is CharacterClass Then Return $"${V.As(CharacterClass).Value.CharacterType.Description}, Count: ${V.As(CharacterClass).Value.Count}"$
	If V Is Range Then Return $"${V.As(Range).First} - ${V.As(Range).Last}"$
	If V Is NamedRange Then Return V.As(NamedRange).Value.Description
	If V Is Set Then Return $"Exclude = ${V.As(Set).Exclude}"$
	If V Is IntersectingSet Then Return "Exclude"
	If V Is Counter Then
		Dim c As Counter = V
		Return $"${CounterDisplay(c.Minimum)} - ${CounterDisplay(c.Maximum)}, ${c.Behavior.Description}"$
	End If	
	If V Is Group Then Return V.As(Group).GroupType.Description
	If V Is Boundary Then Return V.As(Boundary).BoundaryType.Description
	If V Is OrClause Then Return "Or"
	If V Is PatternValue Then Return "[Imported pattern]"
	Return ""
End Sub

Private Sub CounterDisplay(i As Int) As String
	Select i
		Case QtyAny 
			Return "Any"
		Case Else
			If i < 0 Then Return $"Invalid(${i}"$
			Return i
	End Select
End Sub

Private Sub NodeToString(N As TreeNode) As String
	Dim sb As StringBuilder
	sb.Initialize
	sb.Append(IIf(N.Name = "", "-", N.Name)).Append(" ").Append(NodeClass(N)).Append(": ").Append(NodeValueToString(N))
	Return sb.ToString
End Sub

Public Sub Visualize(tv As TreeView)
	tv.Root.Children.Clear
	AddToTreeView(tv.Root.Children, Tree.Contents)
End Sub

Private Sub AddToTreeView(Children As List, Contents As List)
	For Each N As TreeNode In Contents
		Dim ti As TreeItem
		ti.Initialize("", NodeToString(N))
		Children.Add(ti)
		AddToTreeView(ti.Children, N.Contents)
	Next
End Sub

#End Region

#Region Translation

Private Sub Voc(i As Int) As String
	Return RegexVocabularyMap.Get(i)
End Sub

Public Sub RegexPattern As String
	Dim sb As StringBuilder
	sb.Initialize
	If FAnchorToStart Then sb.Append(IIf(FMultiline, Voc(VocAnToOptStart), Voc(VocAnToAbsStart)))
	NodeRegexPattern(Tree, sb)
	If FAnchorToEnd Then sb.Append(IIf(FMultiline, Voc(VocAnToOptEnd), Voc(VocAnToAbsEnd)))
	Return sb.ToSTring
End Sub

Private Sub NodeRegexPattern(N As TreeNode, sb As StringBuilder)
	For Each ChildNode As TreeNode In N.Contents
		Dim V As Object = ChildNode.Value
		If V Is String Then
			sb.Append(IIf(InSet(ChildNode), LiteralInSet(V), Literal(V)))
		Else If V Is CharacterClass Then
      sb.Append(Multiple(V.As(CharacterClass).Value.CharacterType.RegexValue, V.As(CharacterClass).Value.Count))
		Else If V Is Range Then
			Dim R As Range = V
			If NotInSet(ChildNode) Then sb.Append(Voc(VocSetStart))
			sb.Append(R.First).Append(Voc(VocSetSeparator)).Append(R.Last)
			If NotInSet(ChildNode) Then sb.Append(Voc(VocSetEnd))
		Else If V Is NamedRange Then
			Dim NR As NamedRange = V
			If NotInSet(ChildNode) Then sb.Append(Voc(VocSetStart))
			sb.Append(NR.Value.RegexValue)
			If NotInSet(ChildNode) Then sb.Append(Voc(VocSetEnd))
		Else If V Is Set Then
			If NotInSet(ChildNode) Then sb.Append(Voc(VocSetStart))
			If V.As(Set).Exclude Then sb.Append(Voc(VocSetNegate))
			If ChildNode.Contents.Size > 0 Then NodeRegexPattern(ChildNode, sb)
			If NotInSet(ChildNode) Then sb.Append(Voc(VocSetEnd))
		Else If V Is IntersectingSet Then
			sb.Append(Voc(VocIntersectingSetStart))
			If ChildNode.Contents.Size > 0 Then NodeRegexPattern(ChildNode, sb)
			sb.Append(Voc(VocSetEnd))
		Else If V Is Counter Then
			If ChildNode.Contents.Size > 0 Then 
				If NeedsEnclosing(ChildNode) Then sb.Append(Voc(VocGrpStart))
				NodeRegexPattern(ChildNode, sb)
				If NeedsEnclosing(ChildNode) Then sb.Append(Voc(VocGrpEnd))
			End If
			sb.Append(CounterToRegex(V))
		Else If V Is Group Then
			Dim Grp As Group = V
			sb.Append(Voc(VocGrpStart))
			sb.Append(V.As(Group).GroupType.RegexValue)
			If ChildNode.Contents.Size > 0 Then NodeRegexPattern(ChildNode, sb)
			If Grp.GroupType = GrpCountedLookBehind Then sb.Append(Voc(VocQtyStart)).Append(Grp.Minimum).Append(Voc(VocQtySeparator)).Append(Grp.Maximum).Append(Voc(VocQtyEnd))
			sb.Append(Voc(VocGrpEnd))
		Else If V Is Boundary Then
			sb.Append(V.As(Boundary).BoundaryType.RegexValue)
			If ChildNode.Contents.Size > 0 Then
				NodeRegexPattern(ChildNode, sb)
				sb.Append(V.As(Boundary).BoundaryType.RegexValue)
			End If
		Else If V Is OrClause Then			
			sb.Append(Voc(VocAltOr))
			If ChildNode.Contents.Size > 0 Then NodeRegexPattern(ChildNode, sb)
		Else If V Is PatternValue Then
			sb.Append(V.As(PatternValue).Pattern)	
		End If
	Next
End Sub

Private Sub NeedsEnclosing(N As TreeNode) As Boolean
  Return N.Contents.Size > 1 Or NotEnclosing(N.Contents.Get(0))
End Sub

Private Sub FullCountPattern(Minimum As Int, Maximum As Int) As String
  Dim sb As StringBuilder
	sb.Initialize
	sb.Append(Voc(VocQtyStart))
	If Minimum > -1 Then sb.Append(Minimum)
	sb.Append(Voc(VocQtySeparator))
	If Maximum > 0 Then sb.Append(Maximum)
	sb.Append(Voc(VocQtyEnd))
	Return sb.ToString
End Sub

Private Sub ResolveQuantity(Minimum As Int, Maximum As Int) As TQty
	Dim Result As TQty
	Result.Initialize  
	Result.Minimum = Max(Minimum, 0)
	Result.Maximum = Max(Maximum, QtyAny)
	If Result.Maximum > -1 And Result.Maximum < Result.Minimum Then 
		Result.Minimum = Result.Minimum + Result.Maximum
		Result.Maximum = Result.Minimum - Result.Maximum
		Result.Minimum = Result.Minimum - Result.Maximum
	End If
	Return Result
End Sub

Private Sub CounterToRegex(C As Counter) As String
	'If InGroup Then GrpEnd
	Dim sb As StringBuilder
	sb.Initialize
	If C.Minimum = C.Maximum Then
		sb.Append(Voc(VocQtyStart)).Append(C.Minimum).Append(Voc(VocQtyEnd))
	Else
		Select C.Minimum
			Case 0
				Select C.Maximum
					Case QtyAny
						sb.Append(Voc(VocQtyAny))
					Case 1
						sb.Append(Voc(VocQtyOptional))
					Case Else
						sb.Append(FullCountPattern(C.Minimum, C.Maximum))
				End Select
			Case 1	
				If C.Maximum = QtyAny Then sb.Append(Voc(VocQtyAtLEastOne)) Else sb.Append(FullCountPattern(C.Minimum, C.Maximum))
			Case Else
				If C.Maximum = QtyAny Then sb.Append(Voc(VocQtyStart)).Append(C.Minimum).Append(Voc(VocQtySeparator)).Append(Voc(VocQtyEnd)) Else sb.Append(FullCountPattern(C.Minimum, C.Maximum))
		End Select
	End If
	Return sb.Append(C.Behavior.RegexValue).ToString
End Sub

Private Sub LiteralInSet(SetText As String) As String	
	For i = 0 To MetacharactersInSets.Length - 1
		SetText = SetText.Replace(MetacharactersInSets.CharAt(i), "\" & MetacharactersInSets.CharAt(i))
	Next
	Return SetText
End Sub

Private Sub Literal(Text As String) As String	
	If Text.Length = 0 Then Return ""
	Dim i As Int
	Dim Block As Boolean = False
	Dim c As Char
	Dim Count As Int = 0
	For i = 0 To Metacharacters.Length - 1
		c = Metacharacters.CharAt(i)
		For ti = 0 To Text.Length - 1
			If c = Text.CharAt(ti) Then Count = Count + 1
			If Count > 3 Then
				Block = True
				Exit
			End If
		Next
		If Block Then Exit
	Next
	If Block = False Then
		For i = 0 To Metacharacters.Length - 1
			Text = Text.Replace(Metacharacters.CharAt(i), "\" & Metacharacters.CharAt(i))
		Next
		Return Text
	End If
	i = Text.IndexOf("\E")
	If i = -1 Then Return "\Q" & Text & "\E"
	Dim sb As StringBuilder
	sb.Initialize
	sb.Append("\Q")
	Dim Start As Int = 0
	Do While i > -1
		sb.Append(Text.SubString2(Start, i))
		sb.Append("\E\\E\Q")
		Start = i + 2
		i = Text.IndexOf2("\E", Start)
	Loop
	sb.Append(Text.SubString(Start)).Append("\E")
	Return sb.ToString
End Sub

#End Region

#Region Execute

'Like Regex.IsMatch, but uses the internal Pattern.
'Just pass the text to be searched.
'This anchores the match to the text start and end, not for finding matches inside the text!
'Also see HasMatch.
Public Sub ExIsMatch(Txt As String) As Boolean
	Try
		Return Regex.IsMatch2(RegexPattern, Options, Txt)
	Catch
		RCLog($"ExIsMatch: ${LastException.Message}"$, False)
		Return False
	End Try
End Sub

'Like Regex.IsMatch2, but uses the internal Pattern.
'Options are simplified with two Boolean values.
'Just pass the text to be searched.
'This anchores the match to the text start and end, not for finding matches inside the text!
'Also see HasMatch.
Public Sub ExIsMatch2(Txt As String, IgnoreCase As Boolean, Multiline As Boolean) As Boolean
	Try
		Return Regex.IsMatch2(RegexPattern, RegexOptions(IgnoreCase, Multiline), Txt)
	Catch
		RCLog($"ExIsMatch2: ${LastException.Message}"$, False)
		Return False
	End Try
End Sub

'Like Regex.IsMatch, but
'1) Uses the internal pattern.
'2) Unlike IsMatch, returns True if the match is inside the text somewhere.
'Options are simplified with two Boolean values.
'Just pass the text to be searched.
Public Sub ExHasMatch(Txt As String) As Boolean
	Try
		Dim m As Matcher = ExMatcher(Txt)
		If Not(m.IsInitialized) Then Return 0
		Return m.Find
	Catch
		RCLog($"ExHasMatch: ${LastException.Message}"$, False)
		Return False
	End Try
End Sub

'Like Regex.IsMatch2, but
'1) Uses the internal pattern.
'2) Unlike IsMatch, returns True if the match is inside the text somewhere.
'Options are simplified with two Boolean values.
'Just pass the text to be searched.
Public Sub ExHasMatch2(Txt As String, IgnoreCase As Boolean, Multiline As Boolean) As Boolean
	Try
		Dim m As Matcher = ExMatcher2(Txt, IgnoreCase, Multiline)
		If Not(m.IsInitialized) Then Return 0
		Return m.Find
	Catch
		RCLog($"ExHasMatch2: ${LastException.Message}"$, False)
		Return False
	End Try
End Sub

'Counts the number of matches. 
'Matching is done with the internal pattern.
'Just pass the text to be searched.
Public Sub ExMatchCount(Txt As String) As Int
	Dim m As Matcher = ExMatcher(Txt)
	If Not(m.IsInitialized) Then Return 0
	Dim Result As Int = 0
	Do While m.Find
		Result = Result + 1
	Loop
	Return Result
End Sub

'Counts the number of matches.
'Matching is done with the internal pattern, with Regex options.
'Just pass the text to be searched.
Public Sub ExMatchCount2(Txt As String, IgnoreCase As Boolean, Multiline As Boolean) As Int
	Dim m As Matcher = ExMatcher2(Txt, IgnoreCase, Multiline)
	If Not(m.IsInitialized) Then Return 0
	Dim Result As Int = 0
	Do While m.Find
		Result = Result + 1
	Loop
	Return Result
End Sub

'Returns a list with the start indeces of matches.
'Matching is done with the internal pattern.
'Just pass the text to be searched.
Public Sub ExIndeces(Txt As String) As List
	Dim Result As List
	Result.Initialize
	Dim m As Matcher = ExMatcher(Txt)
	If Not(m.IsInitialized) Then Return Result
	Dim i As Int = 1
	Do While m.Find
		Result.Add(m.GetStart(i))
		i = i + 1
	Loop
	Return Result
End Sub

'Returns a list with the start indeces of matches.
'Matching is done with the internal pattern, with Regex options.
'Just pass the text to be searched.
Public Sub ExIndeces2(Txt As String, IgnoreCase As Boolean, Multiline As Boolean) As List
	Dim Result As List
	Result.Initialize
	Dim m As Matcher = ExMatcher2(Txt, IgnoreCase, Multiline)
	If Not(m.IsInitialized) Then Return Result
	Dim i As Int = 1
	Do While m.Find
		Result.Add(m.GetStart(i))
		i = i + 1
	Loop
	Return Result
End Sub

'Like Regex.Matcher, but with the internal Pattern.
'Just pass the text to be searched.
Public Sub ExMatcher(Txt As String) As Matcher
	Try
		Return Regex.Matcher2(RegexPattern, Options, Txt)
	Catch
		RCLog($"ExMatcher: ${LastException.Message}"$, False)
		Return Null
	End Try
End Sub

'Like Regex.Matcher2, but with the internal Pattern.
'Options are simplified with two Boolean values.
'Just pass the text to be searched.
Public Sub ExMatcher2(Txt As String, IgnoreCase As Boolean, Multiline As Boolean) As Matcher
	Try
		Return Regex.Matcher2(RegexPattern, RegexOptions(IgnoreCase, Multiline), Txt)
	Catch
		RCLog($"ExMatcher2: ${LastException.Message}"$, False)
		Return Null
	End Try
End Sub

'Like Regex.Replace, but with the internal Pattern and internal Template.
'Just pass the text to be searched.
Public Sub ExReplace(Txt As String) As String
	Try
		Return Regex.Replace(RegexPattern, Txt, Template.ToString)
	Catch
		RCLog($"ExReplace: ${LastException.Message}"$, False)
		Return ""
	End Try
End Sub

'Like Regex.Replace2, but with the internal Pattern and internal Template.
'Options are simplified with two Boolean values.
'Just pass the text to be searched.
Public Sub ExReplace2(Txt As String, IgnoreCase As Boolean, Multiline As Boolean) As String
	Try
		Return "" ' Regex.Replace2(RegexPattern, GetOptions(IgnoreCase, Multiline), Txt, getReplaceTemplate)
	Catch
		RCLog($"ExReplace2: ${LastException.Message}"$, False)
		Return ""
	End Try
End Sub

'Like Regex.Replace, but with the internal Pattern.
'Just pass the text to be searched and the Replace Template.
Public Sub ExReplaceWith(Txt As String, ReplaceTemplate As String) As String
	Try
		Return Regex.Replace2(RegexPattern, Options, Txt, Template)
	Catch
		RCLog($"ExReplaceWith: ${LastException.Message}"$, False)
		Return ""
	End Try
End Sub

'Like Regex.Replace2, but with the internal Pattern.
'Options are simplified with two Boolean values.
'Just pass the text to be searched and the Replace Template.
Public Sub ExReplaceWith2(Txt As String, IgnoreCase As Boolean, Multiline As Boolean, ReplaceTemplate As String) As String
	Try
		Return Regex.Replace2(RegexPattern, RegexOptions(IgnoreCase, Multiline), Txt, Template)
	Catch
		RCLog($"ExReplaceWith2: ${LastException.Message}"$, False)
		Return ""
	End Try
End Sub

'Like Regex.Split, but with the internal Pattern and internal Splitter pattern.
'Set up a pattern the regular way and then call MovePatternToSplitter.
'Then set up the pattern for matching and call Split.
'Just pass the text to be searched and the Replace Template.
Public Sub ExSplit(Txt As String, RemoveBlanks As Boolean) As String()
	Try
		Dim lst As List
		lst.Initialize2(Regex.Split2(RegexPattern, Options, Txt))
		If RemoveBlanks Then
			For i = lst.Size - 1 To 0 Step -1
				If "" = lst.Get(i) Then lst.RemoveAt(i)
			Next
		End If
		Dim Result(lst.Size) As String
		For Each s As String In lst
			Result(i) = s
		Next
		Return Result
	Catch
		RCLog($"ExSplit: ${LastException.Message}"$, False)
		Return Null
	End Try
End Sub

'Like Regex.Split2, but with the internal Pattern and internal Splitter pattern.
'Set up a pattern the regular way and then call MovePatternToSplitter.
'Then set up the pattern for matching and call Split.
'Options are simplified with two Boolean values.
'Just pass the text to be searched and the Replace Template.
Public Sub ExSplit2(Txt As String, IgnoreCase As Boolean, Multiline As Boolean, RemoveBlanks As Boolean) As String()
	Try
		Dim lst As List
		lst.Initialize2(Regex.Split2(RegexPattern, RegexOptions(IgnoreCase, Multiline), Txt))
		If RemoveBlanks Then
			For i = lst.Size - 1 To 0 Step -1
				If "" = lst.Get(i) Then lst.RemoveAt(i)
			Next
		End If
		Dim Result(lst.Size) As String
		For Each s As String In lst
			Result(i) = s
		Next
		Return Result
	Catch
		RCLog($"ExSplit2: ${LastException.Message}"$, False)
		Return Null
	End Try
End Sub

Private Sub RCLog(Msg As String, Crash As Boolean)
	If Msg = "" Then Return
	Dim T As Long = DateTime.Now
	Log($"$1.0{DateTime.GetHour(T)}:$2.0{DateTime.GetMinute(T)}:$2.0{DateTime.GetSecond(T)}.$3.0{T Mod 1000} - RegexConstructor: ${Msg}"$)
	If Crash Then
		"a".As(String).SubString(-1)
	End If
End Sub

Private Sub Options As Int
  Return Bit.Or(FIgnoreCase, FMultiline)	
End Sub

Private Sub RegexOptions(IgnoreCase As Boolean, Multiline As Boolean) As Int
	Return Bit.Or(IIf(IgnoreCase, Regex.CASE_INSENSITIVE, 0), IIf(Multiline, Regex.MULTILINE, 0))
End Sub

#End Region

#Region Nodes

#Region Create 

Private Sub CreateCharacterClass(Value As CharacterClassValue) As CharacterClass
	Dim Result As CharacterClass
	Result.Initialize
	Result.Value = Value
	Return Result
End Sub

Private Sub CopyTCharacterClass(c As CharacterClass) As CharacterClass
	Return CreateCharacterClass(c.Value)
End Sub

Private Sub CreateCharacterClassNode(Value As CharacterClassValue) As TreeNode
	Return CreateNode(CreateCharacterClass(Value))
End Sub

Public Sub CreateRange(First As String, Last As String) As Range
	Dim Result As Range
	Result.Initialize
	Result.First = First
	Result.Last = Last
	Return Result
End Sub

Private Sub CopyTRange(r As Range) As Range
	Return CreateRange(r.First, r.Last)
End Sub

Private Sub CreateRangeNode(First As String, Last As String) As TreeNode
	Return CreateNode(CreateRange(First, Last))
End Sub

Private Sub CreateNamedRange(Value As NamedRangeType, Exclude As Boolean) As NamedRange
	Dim Result As NamedRange
	Result.Initialize
	Result.Value = Value
	Result.Exclude = Exclude
	Return Result
End Sub

Private Sub CopyTNamedRange(r As NamedRange) As NamedRange
	Return CreateNamedRange(r.Value, r.Exclude)
End Sub
	
Private Sub CreateNamedRangeNode(Value As NamedRangeType, Exclude As Boolean)	As TreeNode
	EnsureName(Value.Description)
	Return CreateNode(CreateNamedRange(Value, Exclude))
End Sub

Private Sub CreateSet(Exclude As Boolean) As Set
	Dim Result As Set
	Result.Initialize
	Result.Exclude = Exclude
	Return Result
End Sub

Private Sub CopyTSet(s As Set) As Set
	Return CreateSet(s.Exclude)
End Sub

Private Sub CreateSetNode(Exclude As Boolean) As TreeNode
	Return CreateNode(CreateSet(Exclude))
End Sub

Private Sub CreateIntersectingSet As IntersectingSet
	Dim Result As IntersectingSet
	Result.Initialize
	Result.Exclude = True
	Return Result
End Sub

Private Sub CreateIntersectingSetNode As TreeNode
	Return CreateNode(CreateIntersectingSet)
End Sub

Private Sub CopyTIntersectingSet As IntersectingSet
	Return CreateIntersectingSet
End Sub

Private Sub CreateCounter(Minimum As Int, Maximum As Int) As Counter
	Dim Result As Counter
	Dim tmp As TQty = ResolveQuantity(Minimum, Maximum)
	Result.Minimum = tmp.Minimum
	Result.Maximum = tmp.Maximum
	Result.Behavior = QtyLong
	Return Result
End Sub

Private Sub CopyTCounter(c As Counter) As Counter
	Dim Result As Counter = CreateCounter(c.Minimum, c.Maximum)
	Result.Behavior = c.Behavior
	Return Result
End Sub

Private Sub CreateCounterNode(Minimum As Int, Maximum As Int) As TreeNode
	Return CreateNode(CreateCounter(Minimum, Maximum))
End Sub

Private Sub CreateGroup(GroupType As GroupType, Minimum As Int, Maximum As Int) As Group
	Dim Result As Group
	Result.Initialize
	Result.GroupType = GroupType
	Result.Minimum = Minimum
	Result.Maximum = Maximum
	Return Result
End Sub

Private Sub CopyTGroup(g As Group) As Group
	Return CreateGroup(g.GroupType, g.Minimum, g.Maximum)
End Sub

Private Sub CreateGroupNode(GroupType As GroupType, Minimum As Int, Maximum As Int) As TreeNode
	Return CreateNode(CreateGroup(GroupType, Minimum, Maximum))
End Sub

Private Sub CreateBoundary(BoundaryType As BoundaryType) As Boundary
	Dim Result As Boundary
	Result.Initialize
	Result.BoundaryType = BoundaryType
	Return Result
End Sub

Private Sub CopyTBoundary(b As Boundary) As Boundary
	Return CreateBoundary(b.BoundaryType)
End Sub

Private Sub CreateBoundaryNode(BoundaryType As BoundaryType) As TreeNode
	Return CreateNode(CreateBoundary(BoundaryType))
End Sub

Private Sub CreateOrClause(Single As Boolean) As OrClause
	Dim Result As OrClause
	Result.Initialize
	Result.Single = Single
	Return Result
End Sub

Private Sub CreateOrNode(Single As Boolean) As TreeNode
	Return CreateNode(CreateOrClause(Single))
End Sub

Private Sub CopyTOrClause(O As OrClause) As OrClause
	Return CreateOrClause(O.Single)
End Sub

Private Sub CreatePatternValue(Pattern As String) As PatternValue
	Dim Result As PatternValue
	Result.Initialize
	Result.Pattern = Pattern
	Return Result
End Sub

Private Sub CreatePatternNode(Pattern As String) As TreeNode
	Return CreateNode(CreatePatternValue(Pattern))
End Sub

#End Region

#Region Node creation

Private Sub CreateNode(Value As Object) As TreeNode
	Dim Result As TreeNode
	Result.Initialize
	Result.Name = NameBuffer.Trim
	NameBuffer = ""
	Result.Value = Value
	Result.Contents.Initialize
	Return Result
End Sub

Private Sub EnsureName(Name As String)
  If NameBuffer.Length = 0 Then NameBuffer = Name	
End Sub

Private Sub CopyNode(N As TreeNode) As TreeNode
	NameBuffer = N.Name
	Dim Result As TreeNode = CreateNode(Null)
	Result.Value = CopyNodeValue(N.Value)
	CopyNodes(N.Contents, Result.Contents)
	Return Result
End Sub

Private Sub CopyNodes(Source As List, Target As List)
	For Each N As TreeNode In Source
		NameBuffer = N.Name
		Dim N2 As TreeNode = CreateNode(CopyNodeValue(N.Value))
		Target.Add(N2)
		CopyNodes(N.Contents, N2.Contents)
	Next
End Sub

Private Sub CopyNodeValue(Value As Object) As Object
	If Value Is String Then Return Value
	If Value Is CharacterClass Then Return CopyTCharacterClass(Value)
	If Value Is Range Then Return CopyTRange(Value)
	If Value Is NamedRange Then Return CopyTNamedRange(Value)
	If Value Is Set Then Return CopyTSet(Value)
	If Value Is IntersectingSet Then Return CopyTIntersectingSet
	If Value Is Counter Then Return CopyTCounter(Value)
	If Value Is Group Then Return CopyTGroup(Value)
	If Value Is Boundary Then Return CopyTBoundary(Value)
	If Value Is OrClause Then Return CopyTOrClause(Value)
	Return ""
End Sub

Private Sub AddToNode(Parent As TreeNode, ANode As TreeNode) As TreeNode
	Parent.Contents.Add(ANode)
	ANode.Parent = Parent
	FlatList.Add(ANode)
	Return ANode
End Sub

#End Region

#Region Node stack

Private Sub PushNode(ANode As TreeNode)
	NodeStack.Add(ANode)
End Sub

Private Sub PullNode
	NodeStack.RemoveAt(NodeStack.Size - 1)
End Sub

Private Sub CurrentNode As TreeNode
	Return NodeStack.Get(NodeStack.Size - 1)
End Sub

Private Sub AddToCurrent(ANode As TreeNode) As TreeNode
	Return AddToNode(CurrentNode, ANode)
End Sub

#End Region

#Region Find node

Private Sub FindNode(Name As String, MustBeOnStack As Boolean) As TreeNode
	Name = Name.Trim
	Dim N As TreeNode
  For i = FlatList.Size - 1 To 0 Step -1
	  N = FlatList.Get(i)
		If N.Name.EqualsIgnoreCase(Name) Then
			If Not(MustBeOnStack) Then Return N 		
			If NodeStack.IndexOf(N) > -1 Then Return N
		End If
  Next
	Return InvalidNode
End Sub

Private Sub FindNodeClass(Class As String, Subclass As Object, MustBeOnStack As Boolean) As TreeNode
	Class = Class.Trim	
	Dim N As TreeNode
	For i = FlatList.Size - 1 To 0 Step -1
		N = FlatList.Get(i)
		If SameClass(N, Class, Subclass) Then
			If Not(MustBeOnStack) Then Return N
			If NodeStack.IndexOf(N) > -1 Then Return N
		End If
	Next
	Return InvalidNode
End Sub

Private Sub FindNamedNodeClass(Name As String, Class As String, Subclass As Object, MustBeOnStack As Boolean) As TreeNode
	Name = Name.Trim
	Class = Class.Trim
	Dim N As TreeNode
	For i = FlatList.Size - 1 To 0 Step -1
		N = FlatList.Get(i)
		If N.Name.EqualsIgnoreCase(Name) And SameClass(N, Class, Subclass) Then
			If Not(MustBeOnStack) Then Return N
			If NodeStack.IndexOf(N) > -1 Then Return N
		End If
	Next
	Return InvalidNode
End Sub

Private Sub NodeClass(N As TreeNode) As String
	Return CleanType(N.Value)
End Sub

Private Sub SameClass(N As TreeNode, Class As String, Subclass As Object) As Boolean
	If Not(NodeClass(N).EqualsIgnoreCase(Class)) Then Return False
	If Subclass = Null Then Return True
	If Class.EqualsIgnoreCase(clsGroup) Then 
		Return N.Value.As(Group).GroupType.Value = Subclass.As(GroupType).Value
	Else If Class.EqualsIgnoreCase(clsBoundary) Then
		Return N.Value.As(Boundary).BoundaryType.Value = Subclass.As(BoundaryType).Value
	End If
	Return True
End Sub

#End Region

#Region Handle node classes 

Private Sub Copy(Name As String, Class As String, Subclass As Object) As TreeNode
	Dim N As TreeNode = FindNamedNodeClass(Name, Class, Subclass, False)
	If N.Name = InvalidName Then
		'Raise hell
		Return InvalidNode
	End If
	Dim Result As TreeNode = CopyNode(N)
	AddToCurrent(Result)
	Return Result
End Sub

Public Sub BeginNamed(Name As String) As RegexConstructor
	Dim N As TreeNode = FindNode(Name, False)
	If N.Name = InvalidName Then
		'Raise hell
		Return Me
	End If
	PushNode(AddToCurrent(CopyNode(N)))
	Return Me
End Sub

Private Sub PullToNode(N As TreeNode) As RegexConstructor
	Do While CurrentNode <> N
		If CurrentNode = Tree Then
			'Raise hell
			Exit
		End If
		PullNode
	Loop
	PullNode
	Return Me
End Sub

Private Sub EndClass(Class As String, Subclass As Object) As RegexConstructor
	Dim N As TreeNode = FindNodeClass(Class, Subclass, True)
	If N.Name = InvalidName Then
		'Raise hell
		Return Me
	End If
	Return PullToNode(N)
End Sub

Private Sub EndNamedClass(Name As String, Class As String, Subclass As Object) As RegexConstructor
	Dim N As TreeNode = FindNamedNodeClass(Name, Class, Subclass, True)
	If N.Name = InvalidName Then
		'Raise hell
		Return Me
	End If
	Return PullToNode(N)
End Sub

Private Sub EnclosingNode(N As TreeNode) As Boolean
	Return IsGroup(N) Or IsSet(N)
End Sub

Private Sub Enclosing(N As TreeNode) As Boolean
	If IsSet(N) Then Return True 'Sets can enclose themselves even if they only have one child node(or none)
  Return EnclosingNode(N) And N.Contents.Size > 0	
End Sub

Private Sub NotEnclosing(N As TreeNode) As Boolean
  Return Not(Enclosing(N))	
End Sub

Private Sub IsGroup(N As TreeNode) As Boolean
	Return LstCaselessIndexOf(GroupNodes, NodeClass(N)) > -1
End Sub

Private Sub InSet(N As TreeNode) As Boolean
	N = N.Parent
	Do While N <> Tree
		If IsSet(N) Then Return True
		N = N.Parent
	Loop
	Return False
End Sub

Private Sub NotInSet(N As TreeNode) As Boolean
  Return Not(InSet(N))	
End Sub

Private Sub IsSet(N As TreeNode) As Boolean
	Return LstCaselessIndexOf(SetNodes, NodeClass(N)) > -1
End Sub

#End Region

#End Region

#Region Pattern functions

'Prepends a name for any element node.
'You can use this name to copy or end the element.
'Group names can be used for the Replace pattern.
Public Sub Named(Name As String) As RegexConstructor
  NameBuffer = Name	
	Return Me
End Sub

#Region Characters, charcter classes and text

'Simply add any text. This text will be "escaped", but you should never use any Regex constructs anyway.
Public Sub TxtAdd(Text As String) As RegexConstructor
	AddToCurrent(CreateNode(Text))
	Return Me
End Sub

'Simply add any text. This text will be "escaped", but you should never use any Regex constructs anyway.
'(You can add a Regex pattern with the PatAdd function.)
'The text will be repeated [Count] times, "Abc", 3 = "AbcAbcAbc".
Public Sub TxtAddMulti(Text As String, Count As Int) As RegexConstructor 'Escaped
	Return TxtAdd(Multiple(Text, Count))
End Sub

'Add a built-in character class like ccAnyNewline. This text will not be "escaped".
'Choose a constant prefixed with ccXXX(regular character classes), ucXXX(Unicode character classes.
'Constants prefixed with SymXXX make it possible to enter symbols not on the keyboard.
'Symbols will be repeated [Count] times, ccAny, 3 = "..."(since the dot is the "any" character in RegEx).
Public Sub TxtAddClass(ClassType As CharacterClassType) As RegexConstructor 'Not escaped
	TxtAddClassMulti(ClassType, 1)
	Return Me
End Sub

'Add a built-in character class like ccAnyNewline. This text will not be "escaped".
'Choose a constant prefixed with ccXXX(regular character classes), ucXXX(Unicode character classes.
'Constants prefixed with SymXXX make it possible to enter symbols not on the keyboard.
'Symbols will be repeated [Count] times, ccAny, 3 = "..."(since the dot is the "any" character in RegEx).
Public Sub TxtAddClassMulti(ClassType As CharacterClassType, Count As Int) As RegexConstructor 'Not escaped
	EnsureName(ClassType.Description)
	AddToCurrent(CreateCharacterClassNode(CreateCharacterClassValue(ClassType, Count)))
	Return Me
End Sub

#End Region

#Region Ranges and sets

'Makes the previous set, predefined range or set exclude its contents.
'Does nothing if the previous element is not a range or set.
Public Sub RngMakeExclude(Exclude As Boolean)
	If FlatList.Size = 0 Then Return
	Dim N As TreeNode = FlatList.Get(FlatList.Size - 1)
	Select NodeClass(N)
		Case clsNamedRange
			N.Value.As(NamedRange).Exclude = Exclude
		Case clsSet
			N.Value.As(Counter).Behavior = QtyShort
	End Select
End Sub

'Makes the previous set, predefined range or set exclude its contents.
'Does nothing if the previous element is not a range or set.
Public Sub SetMakeExclude(Exclude As Boolean)
	RngMakeExclude(Exclude)
End Sub

'Add a range(First, Last like A-C. Ranges are usually added to Sets.
Public Sub RngAdd(First As String, Last As String) As RegexConstructor
	AddToCurrent(CreateRangeNode(First, Last))
	Return Me
End Sub

'Add a predefined range, choose a constant prefixed with RngXXX. Ranges are usually added to Sets. 
Public Sub RngAddPredefined(RangeType As NamedRangeType) As RegexConstructor
	AddToCurrent(CreateNamedRangeNode(RangeType, False))
	Return Me
End Sub

'Begins a Set([...]). Add any text, character, character class, range, count, group. 
'Call EndSet to close the set.
Public Sub SetBegin As RegexConstructor
	PushNode(AddToCurrent(CreateSetNode(False)))
	Return Me
End Sub

'Ends(closes) the last set.
Public Sub SetEnd As RegexConstructor
	Return EndClass(clsSet, Null)
End Sub

'Ends(closes) a set with the name [Name]. This may not be the last set; 
'this command automatically closes anything between.
Public Sub SetEndNamed(Name As String) As RegexConstructor
	Return EndNamedClass(Name, clsSet, Null)
End Sub

'Copy a whole set([...]) with its contents. 
'You can redefine Exclude for the copy.
Public Sub SetCopy(Name As String) As RegexConstructor
	Dim N As TreeNode = Copy(Name, clsSet, Null)
	If N = InvalidNode Then
		
	End If
	Return Me
End Sub

'Begins an "And Not ..." set, like in [A-Z And Not([aeiou])] will exclude vowels.
'Exclude is always True for this set.
Public Sub SetBeginIntersect(Name As String) As RegexConstructor
	PushNode(AddToCurrent(CreateIntersectingSetNode))
	Return Me
End Sub

'Ends(closes) the last intesecting set.
Public Sub SetEndIntersect As RegexConstructor
	Return EndClass(clsIntersectingSet, Null)
End Sub

'Ends(closes) an IntersectingSet with the name [Name]. It may not be the last one; 
'this command automatically closes anything between.
Public Sub SetEndNamedIntersect(Name As String) As RegexConstructor
	Return EndNamedClass(Name, clsIntersectingSet, Null)
End Sub

'Copy a whole IntersectingSet with its contents.
Public Sub SetCopyIntesect(Name As String) As RegexConstructor
	Dim N As TreeNode = Copy(Name, clsIntersectingSet, Null)
	If N = InvalidNode Then
		
	End If
	Return Me
End Sub

#End Region

#Region Counter

'Makes the previous quantifier cut short its search.
'If there is a minimum defined in the quantifier, it will find 
'the [Minimum] number of characters.
'Does nothing if the previous element is not a quantifier.
Public Sub QtyCutShort
	If FlatList.Size = 0 Then Return
	Dim N As TreeNode = FlatList.Get(FlatList.Size - 1)
	If NodeClass(N) <> clsCounter Then Return
	N.Value.As(Counter).Behavior = QtyShort
End Sub
	
'Makes the previous quantifier cut short its search.
'If there is a maximum defined in the quantifier, it will find 
'the [Maximum] number of characters.
'Does nothing if the previous element is not a quantifier.
Public Sub QtyExtend
	If FlatList.Size = 0 Then Return
	Dim N As TreeNode = FlatList.Get(FlatList.Size - 1)
	If NodeClass(N) <> clsCounter Then Return
	N.Value.As(Counter).Behavior = QtyLong
End Sub

'Tells the previous quantifier not to do backtracking.
'You need to learn the Regex backtracking concept to use this.
'Does nothing if the previous element is not a quantifier.	
Public Sub QtyBlockRetries
	If FlatList.Size = 0 Then Return
	Dim N As TreeNode = FlatList.Get(FlatList.Size - 1)
	If NodeClass(N) <> clsCounter Then Return
	N.Value.As(Counter).Behavior = QtyNoRetry
End Sub
	
'Begins a counter. Counters have Minimum, Maximum values, 
'choose -1 or the QtyAny constant if Maximum doesn't matter, like:
'Minimum = 0, Maximum = QtyAny(or Maximum = -1) means "Zero or any number of" 
'character, text, group, etc.
'If Minimum is defined, it is the "At least" value.
'Close the Qty with EndQty.
Public Sub QtyBegin(Minimum As Int, Maximum As Int) As RegexConstructor
	PushNode(AddToCurrent(CreateCounterNode(Minimum, Maximum)))
	Return Me
End Sub

'Appends a counter, meaning: the counter is applied to the previous character, group, etc. 
'Counters have Minimum, Maximum values, choose -1 or the QtyAny constant 
'if Maximum doesn't matter, like: Minimum = 0, Maximum = QtyAny means 
'"Zero or any number of" characterm text, group, etc.
'If Minimum is defined, it is the "At least" value.
Public Sub QtyAppend(Minimum As Int, Maximum As Int) As RegexConstructor
	AddToCurrent(CreateCounterNode(Minimum, Maximum))
	Return Me
End Sub

'Ends(closes) the last Qty. 
Public Sub QtyEnd As RegexConstructor
	Return EndClass(clsCounter, Null)
End Sub

'Ends the last Qty with the name [Name], even if it was not the last Qty. 
'This command closes everything between automatically if necessary. 
Public Sub QtyEndNamed(Name As String) As RegexConstructor
	Return EndNamedClass(Name, clsCounter, Null)
End Sub

'Copies a Qty with all its contents.
Public Sub QtyCopy(Name As String, Minimum As Int, Maximum As Int, Behaviour As QtyBehavior) As RegexConstructor
	Dim N As TreeNode = Copy(Name, clsCounter, Null)
	If N = InvalidNode Then
		
	End If
	Dim c As Counter = N.Value
	If Minimum <> QtyKeep Then c.Minimum = Minimum
	If Maximum <> QtyKeep Then c.Maximum = Maximum
	If Behaviour <> QtyKeepBehavior Then c.Behavior = Behaviour
	Return Me
End Sub

#End Region

#Region Groups

'A simple group is just somethng between brackets.
Public Sub GrpBegin As RegexConstructor
	PushNode(AddToCurrent(CreateGroupNode(GrpRegular, QtyAny, QtyAny)))
	Return Me
End Sub

'Special groups can be selected from the constants prefixed with GrpXXX.
'For Lookahead, non-capturing, etc. groups, you need to understand the Regex concepts.
Public Sub GrpBeginSpecial(GroupType As GroupType) As RegexConstructor
	PushNode(AddToCurrent(CreateGroupNode(GroupType, QtyAny, QtyAny)))
	Return Me
End Sub

'Ends the last group.
Public Sub GrpEnd As RegexConstructor
	Return EndClass(clsGroup, Null)
End Sub

'Ends(closes) the group with name [Name] even if it was not the last one.
'Closes everything else between automatically.
Public Sub GrpEndNamed(Name As String) As RegexConstructor
	Return EndNamedClass(Name, clsGroup, Null)
End Sub

'Copies the closest group with [Name} and all its contents.
Public Sub GrpCopy(Name As String, GroupType As GroupType) As RegexConstructor
	Dim N As TreeNode = Copy(Name, clsGroup, Null)
	If N = InvalidNode Then
		
	End If
	N.Value.As(Group).GroupType = GroupType
	Return Me
End Sub

'Starts a special group with a Minimum/Maximum value. You need to understand
'the Regex concept for this.
Public Sub GrpBeginCountedLookBehind(Minimum As Int, Maximum As Int) As RegexConstructor
	PushNode(AddToCurrent(CreateGroupNode(GrpCountedLookBehind, Minimum, Maximum)))
	Return Me
End Sub

'Ends(closes) a special group with a Minimum/Maximum value. You need to understand
'the Regex concept for this.
Public Sub GrpEndCountedLookBehind As RegexConstructor
	Return EndClass(clsGroup, GrpCountedLookBehind)
End Sub

'Ends(closes) a special group with name [Name] even i it was not the last one.
'Automatically colses everything in between.
Public Sub GrpEndNamedCountedLookBehind(Name As String) As RegexConstructor
	Return EndNamedClass(Name, clsGroup, GrpCountedLookBehind)
End Sub

'Copies the contents of the closest CountedLookBehind group with the name [Name]
Public Sub GrpCopyCountedLookBehind(Name As String, GroupType As GroupType) As RegexConstructor
	Dim N As TreeNode = Copy(Name, clsGroup, GrpCountedLookBehind)
	If N = InvalidNode Then
		
	End If
	N.Value.As(Group).GroupType = GroupType
	Return Me
End Sub

#End Region

#Region Boundaries, Or

'Means that the match must happen at a word boundary.
'Negate means the match cannot happen at a word boundary.
Public Sub BndAddWord(Negate As Boolean) As RegexConstructor 
  AddToCurrent(CreateBoundaryNode(IIf(Negate, BndNotWord, BndWord)))
	Return Me
End Sub

'Begins a "Match at word borders" clause.
'This way you can search for whole words.
'Negate means the match cannot happen at word boundaries.
Public Sub BndBeginWord(Negate As Boolean) As RegexConstructor
	PushNode(AddToCurrent(CreateBoundaryNode(IIf(Negate, BndNotWord, BndWord))))
	Return Me
End Sub

'Ends(closes) the nearest "Match at word borders" clause.
Public Sub BndEndWord As RegexConstructor
	Return EndClass(clsBoundary, BndWord)
End Sub

'Ends(closes) the nearest "Match at word borders" clause with
'the name [Name].
Public Sub BndEndNamedWord(Name As String) As RegexConstructor
	Return EndNamedClass(Name, clsBoundary, BndWord)
End Sub

'Copies the nearest "Match at word borders" clause with
'its contents.
Public Sub BndCopyWord(Name As String) As RegexConstructor
	Dim N As TreeNode = Copy(Name, clsBoundary, BndWord)
	If N = InvalidNode Then
		
	End If
	Return Me
End Sub

'Adds a "Must match at the end of the previous match" command.
Public Sub BndAddMatch As RegexConstructor 
  AddToCurrent(CreateBoundaryNode(BndPrevMatch))
	Return Me
End Sub

'Adds the "Or" character
Public Sub AltOrAdd As RegexConstructor
  AddToCurrent(CreateOrNode(True))
	Return Me	
End Sub

'Begins an "Or" clause.
Public Sub AltOrBegin As RegexConstructor
	PushNode(AddToCurrent(CreateOrNode(False)))
	Return Me
End Sub

'Ends the nearest "Or" clause.
Public Sub AltOrEnd As RegexConstructor
	Return EndClass(clsOrClause, Null)
End Sub

'Ends the nearest "Or" clause wuith the name [Name].
'Automatically closes everything between.
Public Sub AltOrEndNamed(Name As String) As RegexConstructor
	Return EndNamedClass(Name, clsOrClause, Null)
End Sub

'Copies an "Or" clause with its contents.
Public Sub AltOrCopy(Name As String) As RegexConstructor
	Dim N As TreeNode = Copy(Name, clsOrClause, Null)
	If N = InvalidNode Then
		
	End If
	Return Me
End Sub

#End Region

'Inserts an entire Regex pattern like "RealNumbers" into the pattern.
#Region Preset

Public Sub PatAdd(Name As String, Pattern As String) As RegexConstructor
	NameBuffer = Name
	AddToCurrent(CreatePatternNode(Pattern))
	Return Me
End Sub

#End Region

#Region Replacement pattern functions

'Used for replacement. Adds any text to the ReplaceWith template.
Public Sub TempAdd(Text As String) As RegexConstructor
	Template.Append(Text.Replace("\", "\\").Replace("$", "\$").Replace("&", "\&"))
	Return Me
End Sub
	
'Adds a text to the ReplaceWith template with a counter, 	
'so "abc", 2 = "abcabc"
Public Sub TempAddChar(Text As String, Count As Int) As RegexConstructor
  Return TempAdd(Multiple(Text, Count))		
End Sub

'Copies a text from the pattern to the ReplaceWith template, useful for long texts.
Public Sub TempCopyText(Name As String) As RegexConstructor
  Dim N As TreeNode = FindNamedNodeClass(Name, clsText, Null, False)
	Return TempAdd(N.Value)
End Sub

'Adds a symbol not found on the keyboard. Use the SymXXX constants.
Public Sub TempAddSymbol(Class As CharacterClass) As RegexConstructor
  Return TempAdd(Class.Value.CharacterType.Value)	
End Sub

Private Sub GroupIndex(N As TreeNode) As Int
	Dim Result As Int = 0 
	For Each ANode As TreeNode In FlatList
		If NodeClass(ANode) = clsGroup Then Result = Result + 1
		If ANode = N Then Return Result
	Next
	Return -1
End Sub

'Adds a group to the ReplaceWith template.
'Pass a group name from the pattern.
Public Sub TempAddGrp(Name As String) As RegexConstructor
	Dim N As TreeNode = FindNamedNodeClass(Name, clsGroup, Null, False)	
	Template.Append("$").Append(GroupIndex(N))
	Return Me
End Sub

'Adds the "copy the whole match" command to the ReplaceWith template.
'For example, "<" Whole match ">" will enclose a match between "<" and ">".
Public Sub TempAddMatch As RegexConstructor
	Template.Append("$0")
	Return Me
End Sub

#End Region

#End Region
