honey_the_codewitch

Visual FA for Python (work in progress)

Jul 17th, 2025 (edited)
357
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 218.15 KB | None | 0 0
  1. # Visual FA Python port
  2. # copyright (c) 2025 by honey the codewitch
  3. # MIT license
  4. from __future__ import annotations
  5. import math
  6. import os
  7. from subprocess import Popen, PIPE, STDOUT
  8. from collections.abc import Iterator, Callable
  9. class _ParseContext:
  10.     def __init__(self, input, tabWidth = 4, position = 0, line = 1, column = 0):
  11.         self.codepoint = -2
  12.         self.position = 0
  13.         self.line = 1
  14.         self.column = 0
  15.         self.tabWidth = 4
  16.         self.captureBuffer = ""
  17.         self.input = input
  18.         self.tabWidth = tabWidth
  19.         self.position = position
  20.         self.line = line
  21.         self.column = column
  22.    
  23.     def advance(self):
  24.         if self.position >= len(self.input):
  25.             self.codepoint = -1
  26.         else:
  27.             self.codepoint = ord(self.input[self.position])
  28.         match self.codepoint:
  29.             case 10:
  30.                 self.line += 1
  31.                 self.column = 0
  32.             case 13:
  33.                 self.column = 0
  34.             case 9:
  35.                 self.column = (((self.column - 1) / self.tabWidth) + 1) * self.tabWidth + 1
  36.             case _:
  37.                 if self.codepoint > 31:
  38.                     self.column += 1
  39.         self.position += 1
  40.         return self.codepoint
  41.    
  42.     def capture(self):
  43.         self.captureBuffer += chr(self.codepoint)
  44.    
  45.     def ensureStarted(self):
  46.         if self.codepoint == -2:
  47.             self.advance()
  48.        
  49.     @staticmethod
  50.     def _getCodepoints(values):
  51.         result = []
  52.         for v in values:
  53.             result.append(ord(v))    
  54.         return result
  55.  
  56.     def _getErrorMessage(self, expecting):
  57.         sb = ""
  58.         match len(expecting):
  59.             case 0:
  60.                 # do nothing
  61.                 sb = sb
  62.             case 1:
  63.                 sb = ""
  64.                 if expecting[0] == -1:
  65.                     sb += "end of input"
  66.                 else:
  67.                     sb += "\""
  68.                     sb += chr(expecting[0])
  69.                     sb += "\""
  70.             case 2:
  71.                 sb = ""
  72.                 if expecting[0] == -1:
  73.                     sb += "end of input"
  74.                 else:
  75.                     sb += "\""
  76.                     sb += chr(expecting[0])
  77.                     sb += "\""
  78.                
  79.                 sb += " or "
  80.                 if expecting[1] == -1:
  81.                     sb += "end of input"
  82.                 else:
  83.                     sb += "\""
  84.                     sb += chr(expecting[1])
  85.                     sb += "\""
  86.             case _: # length > 2
  87.                 sb = ""
  88.                 if expecting[0] == -1:
  89.                     sb += "end of input"
  90.                 else:
  91.                     sb += "\""
  92.                     sb += chr(expecting[0])
  93.                     sb += "\""
  94.                
  95.                 l = len(expecting) - 1
  96.                 i = 1;
  97.                 while i < l:
  98.                     sb += ", "
  99.                     if -expecting[i] == -1:
  100.                         sb += "end of input"
  101.                     else:
  102.                         sb += "\""
  103.                         sb += chr(expecting[1])
  104.                         sb += "\""
  105.                    
  106.                     i += 1
  107.  
  108.                 sb += ", or "
  109.                 if expecting[i] == -1:
  110.                     sb += "end of input"
  111.                 else:
  112.                     sb += "\""
  113.                     sb += chr(expecting[i])
  114.                     sb += "\""
  115.  
  116.         if self.codepoint == -1:
  117.             if len(expecting) == 0:
  118.                 return "Unexpected end of input"
  119.             return "Unexpected end of input. Expecting " + sb
  120.        
  121.         if len(expecting) == 0:
  122.             return "Unexpected character \"" + chr(self.codepoint) + "\" in input"
  123.         return "Unexpected character \"" + chr(self.codepoint) + "\" in input. Expecting " + sb
  124.    
  125.     def expecting(self, values):
  126.         codepoints = _ParseContext._getCodepoints(values)
  127.         if self.codepoint == -2:
  128.             raise Exception(f"The cursor is before the beginning of the input at {self.position}");
  129.         match len(codepoints):
  130.             case 0:
  131.                 if self.codepoint == -1:
  132.                     raise Exception(f"Unexpected end of input at {self.position}")
  133.             case 1:
  134.                 if codepoints[0] != self.codepoint:
  135.                     raise Exception(f"{self._getErrorMessage(codepoints)} at {self.position}")
  136.             case _:
  137.                 if not (self.codepoint in codepoints):
  138.                     raise Exception(f"{self._getErrorMessage(codepoints)} at {self.position}""IsLetter"] = FACharacterClasses.isLetter
  139.             result["IsDigit"] = FACharacterClasses.isDigit
  140.             result["IsLetterOrDigit"] = FACharacterClasses.isLetterOrDigit
  141.             result["IsWhiteSpace"] = FACharacterClasses.isWhiteSpace
  142.             result["alnum"] = FACharacterClasses.alnum
  143.             result["alpha"] = FACharacterClasses.alpha
  144.             result["cntrl"] = FACharacterClasses.cntrl
  145.             result["digit"] = FACharacterClasses.digit
  146.             result["graph"] = FACharacterClasses.graph
  147.             result["ascii"] = FACharacterClasses.ascii
  148.             result["blank"] = FACharacterClasses.blank
  149.             result["lower"] = FACharacterClasses.lower
  150.             result["print"] = FACharacterClasses.print_
  151.             result["punct"] = FACharacterClasses.punct
  152.             result["space"] = FACharacterClasses.space
  153.             result["upper"] = FACharacterClasses.upper
  154.             result["word"] = FACharacterClasses.word
  155.             result["xdigit"] = FACharacterClasses.xdigit
  156.             FACharacterClasses.__known = result
  157.         return FACharacterClasses.__known
  158.  
  159. class _ExpEdge:
  160.     def __init__(self):
  161.         self.exp = None
  162.         self.fromState = None
  163.         self.toState = None
  164.  
  165. class _FListNode:
  166.     def __init__(self, q, sl):
  167.         self.nextNode = None
  168.         self.prevNode = None
  169.         self.state = q
  170.         self.stateList = sl
  171.         if (sl.count == 0):
  172.             sl.count += 1
  173.             sl.first = self
  174.             sl.last = self
  175.         else:
  176.             sl.count += 1
  177.             sl.last.nextNode = self
  178.             self.prevNode = sl.last
  179.             sl.last = self
  180.    
  181.     def remove(self):
  182.         self.stateList.count -= 1
  183.         if self.stateList.first is self:
  184.             self.stateList.first = self.nextNode
  185.         else:
  186.             self.prevNode.nextNode = self.nextNode
  187.  
  188.         if self.stateList.last is self:
  189.             self.stateList.last = self.prevNode
  190.         else:
  191.             self.nextNode.prevNode = self.prevNode
  192.    
  193. class _FList:
  194.     def __init__(self):
  195.         self.count = 0
  196.         self.first = None
  197.         self.last = None
  198.    
  199.     def add(self, q):
  200.         return _FListNode(q, self)
  201.    
  202. class _FKeyPair:
  203.     def __init__(self, key, value):
  204.         self.key = key
  205.         self.value = value
  206.  
  207. class FADotGraphOptions:
  208.     def __init__(self):
  209.         self.dpi: int = 300
  210.         self.debugString: str | None = None
  211.         self.blockEnds: list[FA] | None = None
  212.         self.debugSourceNfa: FA | None = None
  213.         self.debugShowNfa: bool = False
  214.         self.hideAcceptSymbolIds: bool = False
  215.         self.acceptSymbolNames: list[str] | None = None
  216.         self.vertical: bool = False
  217.         self.statePrefix: str = "q"
  218.  
  219. class FAProgress:
  220.     def __init__(self, callback = None):
  221.         self.value: int = 0
  222.         self._callback = callback
  223.    
  224.     def report(self, value: int) -> None:
  225.         self.value = value
  226.         if self._callback != None:
  227.             self._callback(value)
  228.        
  229. class FARange:
  230.     def __init__(self, min : int = -1, max : int = -1):
  231.         self.min = min
  232.         self.max = max
  233.     def __lt__(self,rhs):
  234.         if self.max == rhs.max:
  235.             return self.min < rhs.min
  236.         return self.max < rhs.max
  237.     def intersects(self, rhs: FARange):
  238.         return (rhs.min >= self.min and rhs.min <= self.max) or (rhs.max >= self.min and rhs.max <= self.max)
  239.     @staticmethod    
  240.     def toUnpacked(packedRanges : list[int]) -> list[FARange]:
  241.         result = []
  242.         length = len(packedRanges)/2
  243.         i = 0
  244.         while i < length:
  245.             j = i * 2
  246.             result.append(FARange(packedRanges[j], packedRanges[j + 1]))
  247.            
  248.             i += 1
  249.         return result
  250.     @staticmethod
  251.     def toPacked(pairs : Iterator[FARange]) -> list[int]:
  252.         result = []
  253.         for pair in pairs:
  254.             result.append(pair.min)
  255.             result.append(pair.max)    
  256.         return result
  257.    
  258. class FATransition:
  259.     def __init__(self, to: FA, min : int = -1, max : int = -1):
  260.         self.to: FA = to
  261.         self.min: int = min
  262.         self.max: int = max
  263.  
  264.     def isEpsilon(self) -> bool:
  265.         return self.min == -1 or self.max == -1
  266.    
  267.     def __lt__(self,rhs):
  268.         if self.max == rhs.max:
  269.             return self.min < rhs.min
  270.         return self.max < rhs.max
  271.    
  272. class FA:
  273.     def __init__(self, acceptSymbol : int = -1):
  274.         self.acceptSymbol: int = acceptSymbol
  275.         self.transitions: list[FATransition] = []
  276.         self.__minimizationTag: int = -1
  277.         self.__isDeterministic: bool = True
  278.         self.__isCompact: bool = True
  279.         self.id: int = -1
  280.         self.__fromStates: list[FA] = []
  281.     def __str__(self):
  282.         if self.id == -1:
  283.             return "FA"
  284.         return f"q{self.id}"
  285.    
  286.     def isCompact(self) -> bool:
  287.         return self.__isCompact
  288.    
  289.     def isDeterministic(self) -> bool:
  290.         return self.__isDeterministic
  291.    
  292.     def isAccepting(self) -> bool:
  293.         return self.acceptSymbol != -1
  294.    
  295.     def isFinal(self) -> bool:
  296.         return len(self.transitions) == 0
  297.    
  298.     def isNeutral(self) -> bool:
  299.         return self.acceptSymbol == -1 and len(self.transitions) == 1 and self.transitions[0].isEpsilon()
  300.    
  301.     def isTrap(self) -> bool:
  302.         return self.acceptSymbol == -1 and len(self.transitions) == 0
  303.    
  304.     def addEpsilon(self, to: FA, compact : bool = True) -> None:
  305.         if compact == True:
  306.             i: int = 0
  307.             while i < len(to.transitions):
  308.                 fat = to.transitions[i]
  309.                 if fat.isEpsilon() == False:
  310.                     self.addTransition(FARange(fat.min, fat.max), fat.to, True)
  311.                 else:
  312.                     self.addEpsilon(fat.to, True)
  313.                 i += 1
  314.             if self.acceptSymbol < 0 and to.acceptSymbol > -1:
  315.                 self.acceptSymbol = to.acceptSymbol
  316.         else:
  317.             found: bool = False
  318.             i: int = 0
  319.             while i < len(to.transitions):
  320.                 fat = to.transitions[i]
  321.                 if fat.isEpsilon() == False:
  322.                     break
  323.                 if fat.to is to:
  324.                     found = True
  325.                     break
  326.                 i += 1
  327.             if found == False:
  328.                 self.transitions.insert(i,FATransition(to))
  329.                 self.__isCompact = False
  330.                 self.__isDeterministic = False
  331.    
  332.     def addTransition(self, rng: FARange, to: FA, compact: bool = True) -> None:
  333.         if rng.min == -1 and rng.max == -1:
  334.             self.addEpsilon(to, compact)
  335.             return
  336.         if rng.min > rng.max:
  337.             tmp = rng.min
  338.             rng.min = rng.max
  339.             rng.max = tmp
  340.         i: int = 0
  341.         insert: int = -1
  342.         while i < len(self.transitions):
  343.             fat = self.transitions[i]
  344.             if to is fat.to and rng.min == fat.min and rng.max == fat.max:
  345.                 return
  346.             if self.__isDeterministic:
  347.                 if rng.intersects(FARange(fat.min, fat.max)):
  348.                     self.__isDeterministic = False
  349.             if rng.max > fat.max:
  350.                 insert = i
  351.             if self.__isDeterministic == False and rng.max < fat.min:
  352.                 break
  353.             i += 1
  354.         self.transitions.insert(insert+1,FATransition(to, rng.min, rng.max))
  355.    
  356.     def clearTransitions(self) -> None:
  357.         self.transitions.clear()
  358.         self.__isDeterministic = True
  359.         self.__isCompact = True
  360.    
  361.     @staticmethod
  362.     def getFirstAcceptSymbol(states: list[FA]) -> int:
  363.         for state in states:
  364.             if state.acceptSymbol != -1:
  365.                 return state.acceptSymbol
  366.         return -1
  367.    
  368.     def fillClosure(self, result: list[FA] | None = None) -> list[FA]:
  369.         if result is None:
  370.             result = []
  371.         if self in result:
  372.             return result
  373.         result.append(self)
  374.         i = 0
  375.         while i < len(self.transitions):
  376.             trn = self.transitions[i]
  377.             trn.to.fillClosure(result)
  378.             i += 1
  379.         return result
  380.    
  381.     def fillEpsilonClosure(self, result : list[FA] | None = None) -> list[FA]:
  382.         if result is None:
  383.             result = []
  384.         if self in result:
  385.             return result
  386.         result.append(self)
  387.         if self.__isCompact == True:
  388.             return result
  389.         for trn in self.transitions:
  390.             if trn.isEpsilon() == True:
  391.                 if trn.to.__isCompact == True:
  392.                     if not (trn.to in result):
  393.                         result.append(trn.to)
  394.                 else:
  395.                     trn.to.fillEpsilonClosure(result)
  396.             else:
  397.                 break
  398.         return result
  399.    
  400.     @staticmethod
  401.     def fillEpsilonClosureAll(states: list[FA], result: list[FA] | None = None) -> list[FA]:
  402.         if result is None:
  403.             result = []
  404.         for state in states:
  405.             state.fillEpsilonClosure(result)
  406.         return result
  407.    
  408.     def clone(self) -> FA:
  409.         closure = self.fillClosure()
  410.         nclosure: list[FA] = []
  411.         for cfa in closure:
  412.             nfa = FA(cfa.acceptSymbol)
  413.             nfa.__isDeterministic = cfa.__isDeterministic
  414.             nfa.__isCompact = cfa.__isCompact
  415.             nfa.__minimizationTag = cfa.__minimizationTag
  416.             nfa.__fromStates = cfa.__fromStates.copy()
  417.             nfa.id = cfa.id
  418.             nclosure.append(nfa)
  419.         i: int = 0
  420.         while i< len(nclosure):
  421.             cfa = closure[i]
  422.             nfa = nclosure[i]
  423.             j: int = 0
  424.             for fat in cfa.transitions:
  425.                 nfa.transitions.append(FATransition(nclosure[closure.index(fat.to)], fat.min, fat.max))
  426.             i += 1
  427.         return nclosure[0]
  428.  
  429.     def totalize(self, closure: list[FA] | None = None) -> None:
  430.         if closure is None:
  431.             closure = self.fillClosure()
  432.        
  433.         s = FA()
  434.         s.transitions.append(FATransition(s, 0, 0x10ffff))
  435.         for p in closure:
  436.             maxi = 0
  437.             sortedTrans = sorted(p.transitions)
  438.             for t in sortedTrans:
  439.                 if t.isEpsilon() == False:
  440.                     if t.min > maxi:
  441.                         p.transitions.append(FATransition(s, maxi, t.min - 1))
  442.                     if t.max + 1 > maxi:
  443.                         maxi = t.max + 1
  444.                    
  445.             if maxi <= 0x10ffff:
  446.                 p.transitions.append(FATransition(s, maxi, 0x10ffff))
  447.            
  448.     @staticmethod
  449.     def _determinize(target: FA, progress: FAProgress = None) -> None:
  450.         target.setIds()
  451.         if progress is None:
  452.             progress = FAProgress()
  453.         prog = 0
  454.         id = 0
  455.         progress.report(0)
  456.         closure = target.fillClosure()
  457.         p = set()
  458.         for ffa in closure:
  459.             p.add(0)
  460.             for trn in ffa.transitions:
  461.                 if trn.isEpsilon() == False:
  462.                     p.add(trn.min)
  463.                     if trn.max < 0x10ffff:
  464.                         p.add(trn.max + 1)
  465.                
  466.         points = sorted(p)
  467.  
  468.         prog += 1
  469.         progress.report(prog)
  470.         sets = dict() # new Dictionary<_KeySet<FA>, _KeySet<FA>>()
  471.         working = [] # new Queue<_KeySet<FA>>();
  472.         dfaMap =  dict() # new Dictionary<_KeySet<FA>, FA>()
  473.         epscl = []
  474.         ecs = [] # new List<FA>()
  475.         efcs = None # List<FA>
  476.         epscl = []
  477.         target.fillEpsilonClosure(epscl)
  478.         i = 0
  479.         initial = list()
  480.         while i < len(epscl):
  481.             efa = epscl[i]
  482.             initial.append(efa)
  483.             i += 1
  484.         fsinit = frozenset(initial)
  485.         sets[fsinit]= initial
  486.        
  487.         working.append(initial)
  488.         result = FA()
  489.         result.__fromStates = list(initial)
  490.         result.id = id
  491.         id += 1
  492.         result.__isDeterministic = True
  493.         for afa in initial:
  494.             if afa.acceptSymbol != -1:
  495.                 result.acceptSymbol = afa.acceptSymbol
  496.                 break
  497.         prog+=1
  498.         progress.report(prog)
  499.         # powerset/subset construction
  500.         dfaMap[frozenset(initial)]= result
  501.         while len(working) > 0:
  502.             # get the nextNode set
  503.             s = working.pop(0)
  504.             # get the nextNode DFA out of the map
  505.             # of (NFA states)->(dfa state)
  506.             dfa = dfaMap.get(frozenset(s))
  507.             # find the first accepting
  508.             # state if any, and assign
  509.             # it to the new DFA
  510.             for q in s:
  511.                 if q.acceptSymbol!=-1:
  512.                     dfa.acceptSymbol = q.acceptSymbol
  513.                     break
  514.             # for each range in the input alphabet
  515.             i = 0
  516.             while i < len(points):
  517.                 pnt = points[i]
  518.                 dststates = list()
  519.                 for c in s:
  520.                     # TODO: Might be able to eliminate the
  521.                     # epsilon closure here. Needs testing
  522.                     ecs.clear()
  523.                     if c.__isCompact == False:
  524.                         c.fillEpsilonClosure(ecs)
  525.                     else:
  526.                         ecs.append(c)
  527.                     j = 0
  528.                     while j < len(ecs):
  529.                         efa = ecs[j]
  530.                         # basically if we intersect somewhere on the input alphabet,
  531.                         # which we should, then we add the destination state(s) to the set
  532.                         k = 0
  533.                         while k < len(efa.transitions):
  534.                             trns = efa.transitions[k]
  535.                             # skip the epsilon closure
  536.                             # we don't need it
  537.                             if trns.isEpsilon() == True:
  538.                                 k += 1
  539.                                 continue
  540.                             # TODO: can probably early out here
  541.                             # if pnt > trns.Max?
  542.                             if trns.min <= pnt and pnt <= trns.max:
  543.                                 if trns.to.__isCompact == True:
  544.                                     if not (trns.to in dststates):
  545.                                         dststates.append(trns.to)
  546.                                 else:
  547.                                     if efcs is None:
  548.                                         efcs = []
  549.                                     efcs.clear()
  550.                                     trns.to.fillEpsilonClosure(efcs)
  551.                                     m = 0
  552.                                     while m < len(efcs):
  553.                                         if not (efcs[m] in dststates):
  554.                                             dststates.append(efcs[m])
  555.                                         m += 1
  556.                             k+= 1
  557.                         j +=1
  558.                 # is this a new set or an
  559.                 # existing one?
  560.                 fset = frozenset(dststates)                
  561.                 if not (fset in sets):
  562.                     sets[fset]=dststates
  563.                     # add another work item
  564.                     working.append(dststates)
  565.                     # make a new DFA state
  566.                     newfa = FA()
  567.                     newfa.__fromStates = list(dststates)
  568.                     newfa.id = id
  569.                     id += 1
  570.                     dfaMap[fset]= newfa
  571.  
  572.                 dst = dfaMap.get(fset)
  573.                 # find the first and last range to insert
  574.                 first = pnt
  575.                 last = -1
  576.                 if i + 1 < len(points):
  577.                     last = (points[i + 1] - 1)
  578.                 else:
  579.                     last = 0x10ffff
  580.                 # this should already be in sorted order
  581.                 # otherwise we'd use addTransition()
  582.                 dfa.transitions.append(FATransition(dst, first, last))
  583.                 prog+=1
  584.                 progress.report(prog)
  585.                 i += 1
  586.                
  587.             prog+=1
  588.             progress.report(prog)
  589.         # remove dead transitions (destinations with no accept)
  590.         for ffa in result.fillClosure():
  591.             itrns = list(ffa.transitions)
  592.             ffa.transitions.clear()
  593.             for trns in itrns:
  594.                 if FA.getFirstAcceptSymbol(trns.to.fillClosure()) != -1:
  595.                     ffa.transitions.append(trns)
  596.             prog += 1
  597.             progress.report(prog)
  598.  
  599.         prog += 1
  600.         progress.report(prog)
  601.         return result
  602.    
  603.     def toDfa(self, progress: FAProgress | None = None) -> FA:
  604.         return self._determinize(self, progress)
  605.    
  606.     def _step(self, input: int) -> FA | None:
  607.         ic = len(self.transitions)
  608.         i = 0
  609.         while i < ic:
  610.             t = self.transitions[i]
  611.             if input >= t.min and input <= t.max:
  612.                 return t.to
  613.             i += 1
  614.         return None
  615.        
  616.     @staticmethod
  617.     def _minimize(a : FA, progress: FAProgress | None) -> FA:
  618.         prog = 0
  619.         if progress is None:
  620.             progress = FAProgress()
  621.         progress.report(prog)
  622.         a = a.toDfa(progress)
  623.         tr = a.transitions
  624.         if len(tr) == 1:
  625.             t = tr[0]
  626.             if t.to is a and t.min == 0 and t.max == 0x10ffff:
  627.                 return a
  628.            
  629.         a.totalize()
  630.         prog += 1
  631.         progress.report(prog)
  632.         cl = a.fillClosure()
  633.         states = []
  634.         mtag = 0
  635.         for q in cl:
  636.             q.__minimizationTag = mtag
  637.             states.append(q)
  638.             mtag += 1
  639.  
  640.         pp = []
  641.         ic = len(cl)
  642.         i = 0
  643.         while i < ic:
  644.             ffa = cl[i]
  645.             pp.append(0)
  646.             for t in ffa.transitions:
  647.                 pp.append(t.min)
  648.                 if t.max < 0x10ffff:
  649.                     pp.append(t.max + 1)
  650.             i += 1
  651.  
  652.         sigma = list(pp)
  653.         sigma.sort()
  654.         # initialize the data structures
  655.         reverse = []
  656.         for s in states:
  657.             arr = [None] * len(sigma)
  658.             reverse.append(arr)
  659.        
  660.         prog += 1
  661.         progress.report(prog)
  662.         reverseNonempty = []
  663.         i = 0
  664.         while i < len(states):
  665.             arr = [False]*len(sigma)
  666.             reverseNonempty.append(arr)
  667.             i += 1
  668.        
  669.         partition = [None]*len(states)
  670.         prog += 1
  671.         progress.report(prog)
  672.         block = [None]*len(states)
  673.         active = []
  674.         i = 0
  675.         while i < len(states):
  676.             arr = [None]*len(sigma)
  677.             active.append(arr)
  678.             i += 1
  679.        
  680.         active2 = []
  681.         i = 0
  682.         while i < len(states):
  683.             arr = [None]*len(sigma)
  684.             active2.append(arr)
  685.             i += 1
  686.  
  687.         pending = []
  688.         pending2 = []
  689.         i = 0
  690.         while i < len(sigma):
  691.             arr = [False]*len(states)
  692.             pending2.append(arr)
  693.             i+= 1
  694.         split = []
  695.         split2 = [False]*len(states)
  696.         refine = []
  697.         refine2 = [False]*len(states)
  698.         splitBlock = [None] * len(states)
  699.         prog += 1
  700.         progress.report(prog)
  701.         q = 0
  702.         while q < len(states):
  703.             splitBlock[q] = []
  704.             partition[q] = []
  705.             x = 0
  706.             while x < len(sigma):
  707.                 reverse[q][x] = []
  708.                 active[q][x]= _FList()
  709.                 x += 1
  710.             q += 1
  711.         # Find the initial partition and reverse edges
  712.         for qq in states:
  713.             j = 0
  714.             if qq.acceptSymbol == -1:
  715.                 j = 1
  716.             partition[j].append(qq)
  717.             block[qq.__minimizationTag] = j
  718.             x = 0
  719.             while x < len(sigma):
  720.                 y = sigma[x]
  721.                 p = qq._step(y)
  722.                 pn = p.__minimizationTag
  723.                 if reverse[pn] != None and reverse[pn][x] != None:
  724.                     reverse[pn][x].append(qq)
  725.                 reverseNonempty[pn][x] = True
  726.                 x += 1
  727.                
  728.             prog += 1
  729.             progress.report(prog)
  730.        
  731.         # initialize the active sets
  732.         j = 0
  733.         while j <= 1:
  734.             x = 0
  735.             while x < len(sigma):
  736.                 part = partition[j]
  737.                 for qq in part:
  738.                     if reverseNonempty[qq.__minimizationTag][x] == True:
  739.                         active2[qq.__minimizationTag][x] = active[j][x].add(qq)
  740.                 x += 1
  741.                 prog += 1
  742.                 progress.report(prog)
  743.             j += 1
  744.        
  745.         # initialize pending
  746.         x = 0
  747.         while x < len(sigma):
  748.             a0 = active[0][x].count
  749.             a1 = active[1][x].count
  750.             j = 1
  751.             if a0 <= a1:
  752.                 j = 0
  753.             pending.append(_FKeyPair(j, x))
  754.             pending2[x][j] = True
  755.             x += 1
  756.        
  757.         # process pending until fixed point
  758.         k = 2
  759.         while len(pending) > 0:
  760.             ip = pending.pop(0)
  761.             p = ip.key
  762.             x = ip.value
  763.             pending2[x][p] = False
  764.             m = active[p][x].first
  765.             while m != None:
  766.                 for s in reverse[m.state.__minimizationTag][x]:
  767.                     if split2[s.__minimizationTag] == False:
  768.                         split2[s.__minimizationTag] = True
  769.                         split.append(s)
  770.                         j = block[s.__minimizationTag]
  771.                         splitBlock[j].append(s)
  772.                         if refine2[j] != True:
  773.                             refine2[j] = True
  774.                             refine.append(j)
  775.                 m = m.nextNode
  776.            
  777.             prog += 1
  778.             progress.report(prog)
  779.             # refine blocks
  780.             for j in refine:
  781.                 if len(splitBlock[j]) < len(partition[j]):
  782.                     b1 = partition[j]
  783.                     b2 = partition[k]
  784.                     e = splitBlock[j]
  785.                     for s in e:
  786.                         b1.pop(b1.index(s)) # b1.splice(b1.indexOf(s), 1);
  787.                         b2.append(s)
  788.                         block[s.__minimizationTag] = k
  789.                         c = 0
  790.                         while c < len(sigma):
  791.                             sn = active2[s.__minimizationTag][c]
  792.                             if sn != None and sn.stateList is active[j][c]:
  793.                                 sn.remove()
  794.                                 active2[s.__minimizationTag][c] = active[k][c].add(s)
  795.                             c += 1
  796.                        
  797.                     # update pending
  798.                     c = 0
  799.                     while c < len(sigma):
  800.                         aj = active[j][c].count
  801.                         ak = active[k][c].count
  802.                         if pending2[c][j]==False and 0 < aj and aj <= ak:
  803.                             pending2[c][j] = True
  804.                             pending.append(_FKeyPair(j, c))
  805.                         else:
  806.                             pending2[c][k] = True
  807.                             pending.append(_FKeyPair(k, c))
  808.                         c += 1
  809.                     k+= 1
  810.                 sbj = splitBlock[j]
  811.                 for s in sbj:
  812.                     split2[s.__minimizationTag] = False
  813.                 refine2[j] = False
  814.                 splitBlock[j].clear()
  815.                 prog += 1
  816.                 progress.report(prog)
  817.    
  818.             split.clear()
  819.             refine.clear()
  820.         prog += 1
  821.         progress.report(prog)
  822.         # make a new state for each equiv class, set initial state
  823.         newstates = []
  824.         n = 0
  825.         while n < k:
  826.             s = FA()
  827.             s.__isDeterministic = False
  828.             newstates.append(s)
  829.             pn = partition[n]
  830.             for q in pn:
  831.                 if q is a:
  832.                     a = s
  833.                 s.id = q.id
  834.                 s.acceptSymbol = q.acceptSymbol
  835.                 s.__minimizationTag = q.__minimizationTag
  836.                 q.__minimizationTag = n
  837.             prog += 1
  838.             progress.report(prog)
  839.             n += 1
  840.         # build transitions and set acceptance
  841.         for s in newstates:
  842.             st = states[s.__minimizationTag]
  843.             s.acceptSymbol = st.acceptSymbol
  844.             for t in st.transitions:
  845.                 s.transitions.append(FATransition(newstates[t.to.__minimizationTag], t.min, t.max))
  846.             prog += 1
  847.             progress.report(prog)
  848.        
  849.         # remove dead transitions
  850.         for ffa in a.fillClosure():
  851.             itrns = list(ffa.transitions)
  852.             ffa.transitions.clear()
  853.             for trns in itrns:
  854.                 if -1 != FA.getFirstAcceptSymbol(trns.to.fillClosure()):
  855.                     ffa.transitions.append(trns)
  856.         return a
  857.    
  858.     def toMinimizedDfa(self, progress: FAProgress | None = None) -> FA:
  859.         return FA._minimize(self, progress)
  860.    
  861.     @staticmethod
  862.     def _concat(lhs: FA, rhs: FA, compact: bool) -> FA:
  863.         acc = filter(lambda value: value.isAccepting(),lhs.fillClosure())
  864.         for afa in acc:
  865.             afa.acceptSymbol = -1
  866.             afa.addEpsilon(rhs, compact)
  867.    
  868.     def toCompact(self) -> FA:
  869.         result = self.clone()
  870.         closure = result.fillClosure()
  871.         done = False
  872.         while done == False:
  873.             done = True
  874.             i = 0
  875.             while i < len(closure):
  876.                 fa = closure[i]
  877.                 j = 0
  878.                 while j < len(fa.transitions):
  879.                     fat = fa.transitions[j]
  880.                     if fat.isEpsilon():
  881.                         del fa.transitions[j]
  882.                         j -=1
  883.                         fa.addEpsilon(fat.to)
  884.                         done = False
  885.                         break
  886.                     j += 1
  887.                 i += 1
  888.             if done == False:
  889.                 closure = closure[0].fillClosure()
  890.                 break
  891.             fa.__isCompact = True
  892.         return result
  893.    
  894.     def setIds(self) -> None:
  895.         i = 0
  896.         for fa in self.fillClosure():
  897.             fa.id = i
  898.             i += 1
  899.     @staticmethod
  900.     def literal(codepoints: Iterator[int], accept: int = 0, compact: bool = True) -> FA:
  901.         result = FA()
  902.         current: FA = result
  903.         for cp in codepoints:
  904.             current.acceptSymbol = -1
  905.             newFa = FA()
  906.             newFa.acceptSymbol = accept
  907.             current.addTransition(FARange(cp, cp), newFa, compact)
  908.             current = newFa
  909.         return result
  910.     @staticmethod
  911.     def charset(ranges: Iterator[FARange], accept: int = 0, compact: bool = True) -> FA:
  912.         result = FA()
  913.         final = FA(accept)
  914.         l = sorted(ranges)
  915.         for rng in l:
  916.             result.addTransition(rng,final,compact)
  917.         return result
  918.     @staticmethod
  919.     def concat(exprs: Iterator[FA], accept: int = 0, compact: bool = True) -> FA:
  920.         result = None
  921.         left = None
  922.         right = None
  923.         for expr in exprs:
  924.             nval = expr.clone()
  925.             if left is None:
  926.                 if result is None:
  927.                     result = nval
  928.                 left = nval
  929.                 continue
  930.             if right is None:
  931.                 right = nval
  932.            
  933.             nval = right.clone()
  934.             FA._concat(left, nval, compact)
  935.         target = result
  936.         if right != None:
  937.             target = right
  938.        
  939.         cl = target.fillClosure()
  940.         for cfa in cl:
  941.             if cfa.acceptSymbol != -1:
  942.                 cfa.acceptSymbol = accept
  943.         if result is None:
  944.             return FA(accept)
  945.         return result
  946.     @staticmethod
  947.     def union(exprs: Iterator[FA], accept: int = 0, compact: bool = True) -> FA:
  948.         result = FA()
  949.         final = FA()
  950.         final.acceptSymbol = accept
  951.         for expr in exprs:
  952.             nfa = expr.clone()
  953.             nacc = nfa.fillClosure()
  954.             for afa in nacc:
  955.                 if afa.acceptSymbol != -1:
  956.                     afa.acceptSymbol = -1
  957.                     afa.addEpsilon(final, compact)
  958.             result.addEpsilon(nfa, compact)
  959.         return result
  960.     @staticmethod
  961.     def optional(expr: Iterator[FA], accept: int = 0, compact: bool = True) -> FA:
  962.         result = expr.clone()
  963.         acc = result.fillClosure()
  964.         for afa in acc:
  965.             if afa.acceptSymbol != -1:
  966.                 afa.acceptSymbol = accept
  967.                 result.addEpsilon(afa, compact)
  968.         return result
  969.     @staticmethod
  970.     def repeat(expr: FA, minOccurs: int = 0, maxOccurs: int = 0, accept: int = 0, compact: bool = True) -> FA:
  971.         expr = expr.clone()
  972.         if minOccurs > 0 and maxOccurs > 0 and minOccurs > maxOccurs:
  973.             raise Exception("Minimum is greater than maximum")
  974.         result = None
  975.         match minOccurs:
  976.             case -1|0:
  977.                 match maxOccurs:
  978.                     case -1|0:
  979.                         result = FA()
  980.                         result.acceptSymbol = accept
  981.                         result.addEpsilon(expr, compact)
  982.                         for afa in expr.fillClosure():
  983.                             if afa.acceptSymbol != -1:
  984.                                 afa.addEpsilon(result, compact)
  985.                         return result
  986.                     case 1:
  987.                         result = FA.optional(expr, accept, compact)
  988.                         return result
  989.                     case _:
  990.                         l = []
  991.                         expr = FA.optional(expr, accept, compact)
  992.                         l.append(expr)
  993.                         i = 1
  994.                         while i < maxOccurs:
  995.                             l.append(expr.clone())
  996.                             i += 1
  997.                         result = FA.concat(l, accept, compact)
  998.                         return result
  999.             case 1:
  1000.                 match maxOccurs:
  1001.                     case -1|0:
  1002.                         result = FA.concat([expr, FA.repeat(expr, 0, 0, accept, compact)], accept, compact)
  1003.                         return result
  1004.                     case 1:
  1005.                         return expr
  1006.                     case _:
  1007.                         result = FA.concat([expr, FA.repeat(expr, 0, maxOccurs - 1, accept, compact)], accept, compact)
  1008.                         return result
  1009.             case _:
  1010.                 match maxOccurs:
  1011.                     case -1|0:
  1012.                         result = FA.concat([FA.repeat(expr, minOccurs, minOccurs, accept, compact), FA.repeat(expr, 0, 0, accept, compact)], accept, compact)
  1013.                         return result
  1014.                     case 1:
  1015.                         raise Exception("Should never get here")
  1016.                     case _:
  1017.                         if minOccurs == maxOccurs:
  1018.                             l = []
  1019.                             expr = FA.optional(expr, accept, compact)
  1020.                             l.append(expr)
  1021.                             i = 1
  1022.                             while i < maxOccurs:
  1023.                                 l.append(expr.clone())
  1024.                                 i += 1
  1025.                             result = FA.concat(l, accept, compact)
  1026.                             return result
  1027.                         result = FA.concat([FA.repeat(expr, minOccurs, minOccurs, accept, compact), FA.repeat(FA.optional(expr, accept, compact), maxOccurs - minOccurs, maxOccurs - minOccurs, accept, compact)], accept, compact)
  1028.                         return result
  1029.    
  1030.     def fillInputTransitionRangesGroupedByState(self, includeEpsilonTransitions: bool = False, result: dict[FA,list[FARange]] = None) -> dict[FA,list[FARange]]:
  1031.         if result is None:
  1032.             result = dict()
  1033.        
  1034.         for fat in self.transitions:
  1035.             if includeEpsilonTransitions or fat.isEpsilon()==False:
  1036.                 res = result.get(fat.to,None)
  1037.                 if res is None:
  1038.                     ndata = [FARange(fat.min, fat.max)]
  1039.                     result[fat.to]= ndata
  1040.                 else:
  1041.                     res.append(FARange(fat.min, fat.max))
  1042.            
  1043.         for values in result.values():
  1044.             values.sort()
  1045.         return result
  1046.    
  1047.     @staticmethod
  1048.     def toUtf32(text: str) -> Iterator[int]:
  1049.         for s in text:
  1050.             yield ord(s)
  1051.  
  1052.     def toArray(self) -> list[int]:
  1053.         fa = self
  1054.         result: list[int] = []
  1055.         closure = fa.fillClosure()
  1056.         stateIndices: list[int] = []
  1057.         i: int = 0
  1058.         while i < len(closure):
  1059.             cfa = closure[i]
  1060.             stateIndices.append(len(result))
  1061.             result.append(cfa.acceptSymbol)
  1062.             itrgp = cfa.fillInputTransitionRangesGroupedByState(True)
  1063.             result.append(len(itrgp))
  1064.             for itr in itrgp.items():
  1065.                 result.append(closure.index(itr[0]))
  1066.                 result.append(len(itr[1]))
  1067.                 for pack in FARange.toPacked(itr[1]):
  1068.                     result.append(pack)
  1069.             i += 1
  1070.         state: int = 0
  1071.         while state < len(result):
  1072.             state += 1
  1073.             tlen = result[state]
  1074.             state += 1
  1075.             i = 0
  1076.             while i < tlen:
  1077.                 result[state] = stateIndices[result[state]]
  1078.                 state += 1
  1079.                 prlen = result[state]
  1080.                 state += 1
  1081.                 state += prlen * 2
  1082.                 i += 1
  1083.         return result
  1084.    
  1085.     @staticmethod
  1086.     def fromArray(arr: list[int]) -> FA:
  1087.         if len(arr)==0:
  1088.             return FA()
  1089.        
  1090.         si = 0
  1091.         states = dict()
  1092.         while si < len(arr):
  1093.             newFa = FA()
  1094.             states[si]= newFa
  1095.             newFa.acceptSymbol = arr[si]
  1096.             si += 1
  1097.             tlen = arr[si]
  1098.             si += 1
  1099.             i = 0
  1100.             while i < tlen:
  1101.                 si += 1
  1102.                 prlen = arr[si]
  1103.                 si += 1
  1104.                 si += prlen * 2
  1105.                 i += 1
  1106.            
  1107.         si = 0
  1108.         while si < len(arr):
  1109.             state = states.get(si)
  1110.             si += 1
  1111.             tlen = arr[si]
  1112.             si += 1
  1113.             i = 0
  1114.             while i < tlen:
  1115.                 tto = arr[si]
  1116.                 si += 1
  1117.                 prlen = arr[si]
  1118.                 si += 1
  1119.                 to = states.get(tto)
  1120.                 j = 0
  1121.                 while j < prlen:
  1122.                     min = arr[si]
  1123.                     si += 1
  1124.                     max = arr[si]
  1125.                     si += 1
  1126.                     state.addTransition(FARange(min, max), to)
  1127.                     j += 1
  1128.                 i += 1
  1129.         return states.get(0)
  1130.    
  1131.     def toLexer(tokens: Iterator[FA], makeDfa: bool = True, compact: bool = True, progress: FAProgress | None = None) -> FA:
  1132.         if makeDfa == True:
  1133.             i: int = 0
  1134.             while i < len(tokens):
  1135.                 tokens[i] = tokens[i].toMinimizedDfa(progress)
  1136.                 i += 1
  1137.         result = FA()
  1138.         if makeDfa == True:
  1139.             for token in tokens:
  1140.                 result.addEpsilon(token.toMinimizedDfa(progress), True)
  1141.         else:
  1142.             for token in tokens:
  1143.                 result.addEpsilon(token, compact)
  1144.         if makeDfa == True:
  1145.             return result.toDfa(progress)
  1146.         else:
  1147.             return result
  1148.  
  1149.     @staticmethod
  1150.     def fillMove(states: list[FA], codepoint: int, result: list[FA] | None = None) -> list[FA]:
  1151.         if result is None:
  1152.             result = []
  1153.         for state in states:
  1154.             for fat in state.transitions:
  1155.                 # epsilon dsts should already be in states:
  1156.                 if fat.isEpsilon():
  1157.                     continue
  1158.                 if codepoint < fat.min:
  1159.                     break
  1160.                 if codepoint <= fat.max:
  1161.                     fat.to.fillEpsilonClosure(result)
  1162.         return result
  1163.    
  1164.     @staticmethod
  1165.     def __normalizeSortedRangeList(pairs):
  1166.         i = 1
  1167.         while i < len(pairs):
  1168.             if pairs[i - 1].max + 1 >= pairs[i].min:
  1169.                 nr = FARange(pairs[i - 1].min, pairs[i].max)
  1170.                 pairs[i - 1] = nr
  1171.                 del pairs[i]
  1172.                 i -= 1 # compensated for by i += 1 in for loop
  1173.             i += 1
  1174.         i = 1
  1175.     @staticmethod
  1176.     def __fromHexChar(h):
  1177.         if ord(":") > h and ord("/") < h:
  1178.             return h - ord("0")
  1179.         if ord("G") > h and ord("@") < h:
  1180.             return h - ord("7") # 'A'-10
  1181.         if ord("g") > h and ord("`") < h:
  1182.             return h - ord("W") # 'a'-10
  1183.         raise Exception("The value was not hex.")
  1184.    
  1185.     @staticmethod
  1186.     def __isHexChar(h):
  1187.         if ord(":") > h and ord("/") < h:
  1188.             return True
  1189.  
  1190.         if ord("G") > h and ord("@") < h:
  1191.             return True
  1192.  
  1193.         if ord("g") > h and ord("`") < h:
  1194.             return True
  1195.         return False
  1196.    
  1197.     @staticmethod
  1198.     def __parseModifier(expr, pc, accept, compact):
  1199.         if pc.codepoint == -1:
  1200.             return expr
  1201.         position = pc.position
  1202.         match chr(pc.codepoint):
  1203.             case "*":
  1204.                 expr = FA.repeat(expr, 0, 0, accept, compact)
  1205.                 pc.advance()
  1206.             case "+":
  1207.                 expr = FA.repeat(expr, 1, 0, accept, compact)
  1208.                 pc.advance()
  1209.             case "?":
  1210.                 expr = FA.optional(expr, accept, compact)
  1211.                 pc.advance()
  1212.             case "{":
  1213.                 pc.advance()
  1214.                 pc.trySkipWhiteSpace()
  1215.                 pc.expecting(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ',', '}'])
  1216.                 min = -1
  1217.                 max = -1
  1218.                 if ord(",") != pc.codepoint and ord("}") != pc.codepoint:
  1219.                     l = len(pc.captureBuffer)
  1220.                     if pc.tryReadDigits() == False:
  1221.                         pc.expecting(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])
  1222.                     min = int(pc.captureBuffer[l:])
  1223.                     pc.trySkipWhiteSpace()
  1224.                
  1225.                 if ord(",") == pc.codepoint:
  1226.                     pc.advance()
  1227.                     pc.trySkipWhiteSpace()
  1228.                     pc.expecting(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '}'])
  1229.                     if ord("}") != pc.codepoint:
  1230.                         l = len(pc.captureBuffer)
  1231.                         pc.tryReadDigits()
  1232.                         max = int(pc.captureBuffer[l:])
  1233.                         pc.trySkipWhiteSpace()
  1234.                 else:
  1235.                     max = min
  1236.                 pc.expecting(['}'])
  1237.                 pc.advance()
  1238.                 expr = FA.repeat(expr, min, max, accept, compact)
  1239.         return expr
  1240.     @staticmethod
  1241.     def __parseEscapePart(pc):
  1242.         if -1 == pc.codepoint:
  1243.             return -1
  1244.         match chr(pc.codepoint):
  1245.             case "f":
  1246.                 pc.advance()
  1247.                 return ord("\f")
  1248.             case "v":
  1249.                 pc.advance()
  1250.                 return ord("\v")
  1251.             case "t":
  1252.                 pc.advance()
  1253.                 return ord("\t")
  1254.             case "n":
  1255.                 pc.advance()
  1256.                 return ord("\n")
  1257.             case "r":
  1258.                 pc.advance()
  1259.                 return ord("\r")
  1260.             case "x":
  1261.                 if -1 == pc.advance() or FA.__isHexChar(pc.codepoint) == False:
  1262.                     return ord("x")
  1263.                 b = FA.__fromHexChar(pc.codepoint)
  1264.                 if -1 == pc.advance() or FA.__isHexChar(pc.codepoint) == False:
  1265.                     return b
  1266.                 b <<= 4
  1267.                 b |= FA.__fromHexChar(pc.codepoint)
  1268.                 if -1 == pc.advance() or FA.__isHexChar(pc.codepoint) == False:
  1269.                     return b
  1270.                 b <<= 4
  1271.                 b |= FA.__fromHexChar(pc.codepoint)
  1272.                 if -1 == pc.advance() or FA.__isHexChar(pc.codepoint) == False:
  1273.                     return b
  1274.                 b <<= 4
  1275.                 b |= FA.__fromHexChar(pc.codepoint)
  1276.                 return b
  1277.             case "u":
  1278.                 if -1 == pc.advance():
  1279.                     return ord("u")
  1280.                 u = FA.__fromHexChar(pc.codepoint)
  1281.                 u <<= 4
  1282.                 if -1 == pc.advance():
  1283.                     return u
  1284.                 u |= FA.__fromHexChar(pc.codepoint)
  1285.                 u <<= 4
  1286.                 if -1 == pc.advance():
  1287.                     return u
  1288.                 u |= FA.__fromHexChar(pc.codepoint)
  1289.                 u <<= 4
  1290.                 if -1 == pc.advance():
  1291.                     return u
  1292.                 u |= FA.__fromHexChar(pc.codepoint)
  1293.                 return u
  1294.             case _:
  1295.                 i = pc.codepoint
  1296.                 pc.advance()
  1297.                 return i
  1298.        
  1299.     @staticmethod
  1300.     def __parseRangeEscapePart(pc):
  1301.         if pc.codepoint == -1:
  1302.             return -1
  1303.         match chr(pc.codepoint):
  1304.             case "0":
  1305.                 pc.advance()
  1306.                 return ord("\0")
  1307.             case "f":
  1308.                 pc.advance()
  1309.                 return ord("\f")
  1310.             case "v":
  1311.                 pc.advance()
  1312.                 return ord("\v")
  1313.             case "t":
  1314.                 pc.advance()
  1315.                 return ord("\t")
  1316.             case "n":
  1317.                 pc.advance()
  1318.                 return ord("\n")
  1319.             case "r":
  1320.                 pc.advance()
  1321.                 return ord("\r")
  1322.             case "x":
  1323.                 if -1 == pc.advance() or FA.__isHexChar(pc.codepoint) == False:
  1324.                     return ord("x")
  1325.                 b = FA.__fromHexChar(pc.codepoint)
  1326.                 if -1 == pc.advance() or FA.__isHexChar(pc.codepoint) == False:
  1327.                     return b
  1328.                 b <<= 4
  1329.                 b |= FA.__fromHexChar(pc.codepoint)
  1330.                 if -1 == pc.advance() or FA.__isHexChar(pc.codepoint) == False:
  1331.                     return b
  1332.                 b <<= 4
  1333.                 b |= FA.__fromHexChar(pc.codepoint)
  1334.                 if -1 == pc.advance() or FA.__isHexChar(pc.codepoint) == False:
  1335.                     return b
  1336.                 b <<= 4
  1337.                 b |= FA.__fromHexChar(pc.codepoint)
  1338.                 return b
  1339.             case "u":
  1340.                 if -1 == pc.advance():
  1341.                     return ord("u")
  1342.                 u = FA.__fromHexChar(pc.codepoint)
  1343.                 u <<= 4
  1344.                 if -1 == pc.advance():
  1345.                     return u
  1346.                 u |= FA.__fromHexChar(pc.codepoint)
  1347.                 u <<= 4
  1348.                 if -1 == pc.advance():
  1349.                     return u
  1350.                 u |= FA.__fromHexChar(pc.codepoint)
  1351.                 u <<= 4
  1352.                 if -1 == pc.advance():
  1353.                     return u
  1354.                 u |= FA.__fromHexChar(pc.codepoint)
  1355.                 return u
  1356.             case _:
  1357.                 i = pc.codepoint
  1358.                 pc.advance()
  1359.                 return i
  1360.        
  1361.     @staticmethod
  1362.     def __parseSet(pc):
  1363.         result = []
  1364.         pc.ensureStarted()
  1365.         pc.expecting(['['])
  1366.         pc.advance()
  1367.         pc.expecting([])
  1368.         firstRead = True
  1369.         firstChar = 0
  1370.         readFirstChar = False
  1371.         wantRange = False
  1372.         isNot = False
  1373.         if ord("^") == pc.codepoint:
  1374.             isNot = True
  1375.             pc.advance()
  1376.             pc.expecting([])
  1377.        
  1378.         while -1 != pc.codepoint and (firstRead == True or ord("]") != pc.codepoint):
  1379.             if wantRange == False:
  1380.                 # can be a single char,
  1381.                 # a range
  1382.                 # or a named character class
  1383.                 if ord("[") == pc.codepoint: # named char class
  1384.                     epos = pc.position
  1385.                     pc.advance()
  1386.                     pc.expecting([])
  1387.                     if ord(":") != pc.codepoint:
  1388.                         firstChar = ord("[")
  1389.                         readFirstChar = True
  1390.                     else:
  1391.                         firstRead = False
  1392.                         pc.advance()
  1393.                         pc.expecting([])
  1394.                         ll = len(pc.captureBuffer)
  1395.                         if pc.tryReadUntil(ord(":"), False) == False:
  1396.                             raise Exception(f"Expecting character class at {pc.position}")
  1397.                         pc.expecting([':'])
  1398.                         pc.advance()
  1399.                         pc.expecting([']'])
  1400.                         pc.advance()
  1401.                         cls = pc.captureBuffer[ll:]
  1402.                         ranges = FACharacterClasses.known().get(cls,None);
  1403.                         if ranges==None:
  1404.                             raise Exception(f"Unknown character class \"{cls}\" specified at {epos}")
  1405.                        
  1406.                         for rng in FARange.toUnpacked(ranges):
  1407.                             result.append(rng)
  1408.                        
  1409.                         readFirstChar = False
  1410.                         wantRange = False
  1411.                         firstRead = False
  1412.                         continue
  1413.                 if readFirstChar == False:
  1414.                     if ord("\\") == pc.codepoint:
  1415.                         pc.advance()
  1416.                         firstChar = FA.__parseRangeEscapePart(pc)
  1417.                     else:
  1418.                         firstChar = pc.codepoint
  1419.                         pc.advance()
  1420.                         pc.expecting([])
  1421.                     readFirstChar = True
  1422.                 else:
  1423.                     if ord("-") == pc.codepoint:
  1424.                         pc.advance()
  1425.                         pc.expecting([])
  1426.                         wantRange = True
  1427.                     else:
  1428.                         result.append(FARange(firstChar, firstChar))
  1429.                         readFirstChar = False
  1430.                 firstRead = False
  1431.             else:
  1432.                 if ord("\\") != pc.codepoint:
  1433.                     ch = pc.codepoint
  1434.                     pc.advance()
  1435.                     pc.expecting([])
  1436.                     result.append(FARange(firstChar, ch))
  1437.                 else:
  1438.                     min = firstChar
  1439.                     pc.advance()
  1440.                     result.append(FARange(min, FA.__parseRangeEscapePart(pc)))
  1441.                
  1442.                 wantRange = False
  1443.                 readFirstChar = False
  1444.  
  1445.         if readFirstChar == True:
  1446.             result.append(FARange(firstChar, firstChar))
  1447.             if wantRange == True:
  1448.                 result.append(FARange(ord("-"),ord("-")))
  1449.            
  1450.         pc.expecting(["]"])
  1451.         pc.advance()
  1452.         return (isNot, result)
  1453.     @staticmethod
  1454.     def __invertRanges(ranges: list[FARange]):
  1455.         if ranges is None:
  1456.             return
  1457.         last = 0x10ffff
  1458.         e = iter(ranges)
  1459.         n = next(e,None)
  1460.         if n is None:
  1461.             yield FARange(0,0x10ffff)
  1462.             return
  1463.         if n.min > 0:
  1464.             yield FARange(0,n.min - 1)
  1465.             last = n.max
  1466.             if 0x10ffff <= last:
  1467.                 return
  1468.         elif n.min == 0:
  1469.             last = n.max
  1470.             if 0x10ffff <= last:
  1471.                 return
  1472.         n = next(e, None)
  1473.         while not (n is None):
  1474.             if (0x10ffff <= last):
  1475.                 return
  1476.             if last + 1 < n.min:
  1477.                 yield FARange(last+1,n.min-1)
  1478.            
  1479.             last = n.max
  1480.             n = next(e, None)
  1481.         if 0x10ffff > last:
  1482.             yield FARange(last+1,0x10ffff)
  1483.        
  1484.     @staticmethod
  1485.     def __toNotRanges(ranges):
  1486.         if ranges is None:
  1487.             return
  1488.         # expects ranges to be normalized
  1489.         last = 0x10ffff
  1490.         if hasattr(ranges,'__iter__'):
  1491.             e = iter(ranges)
  1492.         else:
  1493.             e = ranges
  1494.         cur = next(e, None)
  1495.         if cur is None:
  1496.             yield FARange(0x0, 0x10ffff)
  1497.             return
  1498.         if cur.min > 0:
  1499.             yield FARange(0, cur.min - 1)
  1500.             last = cur.max
  1501.             if  0x10ffff <= last:
  1502.                 return
  1503.         elif cur.min == 0:
  1504.             last = cur.max
  1505.             if 0x10ffff <= last:
  1506.                 return
  1507.         while not (cur is None):
  1508.             if 0x10ffff <= last:
  1509.                 return
  1510.             if last + 1 < cur.min:
  1511.                 yield FARange(last + 1, (cur.min - 1))
  1512.             last = cur.max
  1513.             cur = next(e, None)
  1514.        
  1515.         if 0x10ffff >= last:
  1516.             yield FARange((last + 1), 0x10ffff)
  1517.    
  1518.     @staticmethod
  1519.     def __parse(pc, accept = 0, compact = True):
  1520.         result = None
  1521.         nextExpr = None
  1522.         ich = 0
  1523.         pc.ensureStarted()
  1524.         while pc.codepoint != -1:          
  1525.             match chr(pc.codepoint):
  1526.                 #case -1:
  1527.                 #    if result is None:
  1528.                 #        # empty string
  1529.                 #        result = FA(accept)
  1530.                 #    return result
  1531.                 case ".":
  1532.                     dot = FA.charset([FARange(0, 0x10ffff)], accept, compact)
  1533.  
  1534.                     pc.advance()
  1535.  
  1536.                     dot = FA.__parseModifier(dot, pc, accept, compact)
  1537.                     if result is None:
  1538.                         result = dot
  1539.                     else:
  1540.                         result = FA.concat([result, dot], accept, compact)
  1541.                    
  1542.                 case "\\":
  1543.                     pc.advance()
  1544.                     pc.expecting([])
  1545.                     isNot = False
  1546.                     ch = chr(pc.codepoint)
  1547.                     match ch:
  1548.                         case ch if ch in ["P", "p"]:
  1549.                             if pc.codepoint==ord("P"):
  1550.                                 isNot = True
  1551.                             pc.advance()
  1552.                             pc.expecting(['{'])
  1553.                             uc = ""
  1554.                             while -1 != pc.advance() and ord("}")!=pc.codepoint:
  1555.                                 uc += chr(pc.codepoint)
  1556.                             pc.expecting(['}'])
  1557.                             pc.advance()
  1558.                             uci = 0
  1559.                             match uc:
  1560.                                 case "Pe":
  1561.                                     uci = 21
  1562.                                 case "Pc":
  1563.                                     uci = 19
  1564.                                 case "Cc":
  1565.                                     uci = 14
  1566.                                 case "Sc":
  1567.                                     uci = 26
  1568.                                 case "Pd":
  1569.                                     uci = 19
  1570.                                 case "Nd":
  1571.                                     uci = 8
  1572.                                 case "Me":
  1573.                                     uci = 7
  1574.                                 case "Pf":
  1575.                                     uci = 23
  1576.                                 case "Cf":
  1577.                                     uci = 15
  1578.                                 case "Pi":
  1579.                                     uci = 22
  1580.                                 case "Nl":
  1581.                                     uci = 9
  1582.                                 case "Zl":
  1583.                                     uci = 12
  1584.                                 case "Ll":
  1585.                                     uci = 1
  1586.                                 case "Sm":
  1587.                                     uci = 25
  1588.                                 case "Lm":
  1589.                                     uci = 3
  1590.                                 case "Sk":
  1591.                                     uci = 27
  1592.                                 case "Mn":
  1593.                                     uci = 5
  1594.                                 case "Ps":
  1595.                                     uci = 20
  1596.                                 case "Lo":
  1597.                                     uci = 4
  1598.                                 case "Cn":
  1599.                                     uci = 29
  1600.                                 case "No":
  1601.                                     uci = 10
  1602.                                 case "Po":
  1603.                                     uci = 24
  1604.                                 case "So":
  1605.                                     uci = 28
  1606.                                 case "Zp":
  1607.                                     uci = 13
  1608.                                 case "Co":
  1609.                                     uci = 17
  1610.                                 case "Zs":
  1611.                                     uci = 11
  1612.                                 case "Mc":
  1613.                                     uci = 6
  1614.                                 case "Cs":
  1615.                                     uci = 16
  1616.                                 case "Lt":
  1617.                                     uci = 2
  1618.                                 case "Lu":
  1619.                                     uci = 0
  1620.                            
  1621.                             if isNot:
  1622.                                 nextExpr = FA.charset(FARange.toUnpacked(FACharacterClasses.unicodeCategories[uci]), accept, compact)
  1623.                             else:
  1624.                                 nextExpr = FA.charset(FARange.toUnpacked(FACharacterClasses.notUnicodeCategories[uci]), accept, compact)
  1625.                         case "d":
  1626.                             nextExpr = FA.charset(FARange.toUnpacked(FACharacterClasses.digit), accept, compact)
  1627.                             pc.advance()
  1628.                         case "D":
  1629.                             nextExpr = FA.charset(FA.__toNotRanges(FARange.toUnpacked(FACharacterClasses.digit)), accept, compact)
  1630.                             pc.advance()
  1631.                         case "s":
  1632.                             nextExpr = FA.charset(FARange.toUnpacked(FACharacterClasses.space), accept, compact)
  1633.                             pc.advance()
  1634.                         case "S":
  1635.                             nextExpr = FA.charset(FA.__toNotRanges(FARange.toUnpacked(FACharacterClasses.space)), accept, compact)
  1636.                             pc.advance()
  1637.                         case "w":
  1638.                             nextExpr = FA.charset(FARange.toUnpacked(FACharacterClasses.word), accept, compact);
  1639.                             pc.advance()
  1640.                         case "W":
  1641.                             nextExpr = FA.charset(FA.__toNotRanges(FARange.toUnpacked(FACharacterClasses.word)), accept, compact);
  1642.                             pc.advance()
  1643.                         case _:
  1644.                             ich = FA.__parseEscapePart(pc)
  1645.                             if -1 != ich:
  1646.                                 nextExpr = FA.literal([ich], accept, compact)
  1647.                             else:
  1648.                                 pc.expecting([]) # throw an error
  1649.                            
  1650.                     nextExpr = FA.__parseModifier(nextExpr, pc, accept, compact)
  1651.                     if not (result is None):
  1652.                         result = FA.concat([result, nextExpr], accept, compact)
  1653.                     else:
  1654.                         result = nextExpr
  1655.                 case ")":
  1656.                     return result
  1657.                 case "(":
  1658.                     pc.advance()
  1659.                     if pc.codepoint == ord("?"):
  1660.                         pc.advance()
  1661.                         pc.expecting([':'])
  1662.                         pc.advance()
  1663.                    
  1664.                     pc.expecting([])
  1665.                     nextExpr = FA.__parse(pc, accept, compact)
  1666.                     pc.expecting([')'])
  1667.                     if -1 == pc.advance():
  1668.                         if result is None:
  1669.                             return nextExpr
  1670.                         else:
  1671.                             return FA.concat([result, nextExpr], accept, compact)
  1672.                        
  1673.                     nextExpr = FA.__parseModifier(nextExpr, pc, accept, compact)
  1674.                     if result is None:
  1675.                         result = nextExpr
  1676.                     else:
  1677.                         result = FA.concat([result, nextExpr], accept, compact)
  1678.                 case "|":
  1679.                     if -1 != pc.advance():
  1680.                         nextExpr = FA.__parse(pc, accept, compact)
  1681.                         result = FA.union([result, nextExpr], accept, compact)
  1682.                     else:
  1683.                         result = FA.optional(result, accept, compact)
  1684.                 case "[":
  1685.                     seti = FA.__parseSet(pc)
  1686.                     sortset = seti[1]
  1687.                     sortset.sort()
  1688.                     FA.__normalizeSortedRangeList(sortset)
  1689.                     if seti[0] == True:
  1690.                         sortset = list(FA.__toNotRanges(sortset))
  1691.                     nextExpr = FA.charset(sortset, accept)
  1692.                     nextExpr = FA.__parseModifier(nextExpr, pc, accept, compact)
  1693.                     if result is None:
  1694.                         result = nextExpr
  1695.                     else:
  1696.                         result = FA.concat([result, nextExpr], accept, compact)
  1697.                 case _:
  1698.                     ich = pc.codepoint
  1699.                     nextExpr = FA.literal([ich], accept, compact)
  1700.                     pc.advance()
  1701.                     nextExpr = FA.__parseModifier(nextExpr, pc, accept, compact)
  1702.                     if result is None:
  1703.                         result = nextExpr
  1704.                     else:
  1705.                         result = FA.concat([result, nextExpr], accept, compact)
  1706.         return result
  1707.     @staticmethod
  1708.     def parse(expression: str, accept: int = 0, compact: bool = True) -> FA:
  1709.         pc = _ParseContext(expression)
  1710.         result = FA.__parse(pc, accept, compact)
  1711.         if result is None:
  1712.             result = FA(accept)
  1713.         return result
  1714.     @staticmethod
  1715.     def acceptingFilter(state: FA) -> bool:
  1716.         return state.acceptSymbol != -1
  1717.     @staticmethod
  1718.     def finalFilter(state: FA) -> bool:
  1719.         return state.isFinal()
  1720.     @staticmethod
  1721.     def neutralFilter(state: FA) -> bool:
  1722.         return state.isNeutral()
  1723.     @staticmethod
  1724.     def trapFilter(state: FA) -> bool:
  1725.         return state.isTrap()
  1726.  
  1727.     def fillFind(self, predicate: Callable[[FA], bool], result: list[FA] = None) -> list[FA]:
  1728.         if result is None:
  1729.             result = []
  1730.         if self in result:
  1731.             return result
  1732.         if predicate(self) == True:
  1733.             result.append(self)
  1734.         for trn in self.transitions:
  1735.             trn.to.fillFind(predicate, result)
  1736.         return result
  1737.    
  1738.     def findFirst(self, predicate: Callable[[FA], bool]) -> FA | None:
  1739.         if predicate(self) == True:
  1740.             return self
  1741.         for trn in self.transitions:
  1742.             result = trn.to.findFirst(predicate)
  1743.             if not (result is None):
  1744.                 return result
  1745.         return None
  1746.     @staticmethod
  1747.     def __appendCodepointTo(codepoint, result):
  1748.         ch = chr(codepoint)
  1749.         match ch:
  1750.             case '.'|'['|']'|'^'|'-'|'+'|'?'|'('|')'|'\\':
  1751.                 result += '\\'
  1752.                 result += ch
  1753.                 return;
  1754.             case '\t':
  1755.                 result += "\\t"
  1756.             case '\n':
  1757.                 result += "\\n"
  1758.             case '\r':
  1759.                 result += "\\r"
  1760.             case '\0':
  1761.                 result +="\\0"
  1762.             case '\f':
  1763.                 result += "\\f"
  1764.             case '\v':
  1765.                 result += "\\v"
  1766.             case '\b':
  1767.                 result += "\\b"
  1768.             case _:
  1769.                 if codepoint<32 or codepoint>127:
  1770.                     if len(ch) == 1:
  1771.                         result += "\\u"
  1772.                         result += format(codepoint,'04x')
  1773.                     else:
  1774.                         result += "\\U"
  1775.                         result += format(codepoint,'08x')
  1776.                 else:
  1777.                     result += ch
  1778.         return result
  1779.    
  1780.     @staticmethod
  1781.     def __appendRangeCodepointTo(codepoint, result):
  1782.         ch = chr(codepoint)
  1783.         match ch:
  1784.             case '.'|'['|']'|'^'|'-'|'('|')'|'{'|'}'|'\\':
  1785.                 result += '\\'
  1786.                 result += ch
  1787.             case '\t':
  1788.                 result += "\\t"
  1789.             case '\n':
  1790.                 result += "\\n"
  1791.             case '\r':
  1792.                 result += "\\r"
  1793.             case '\0':
  1794.                 result +="\\0"
  1795.             case '\f':
  1796.                 result += "\\f"
  1797.             case '\v':
  1798.                 result += "\\v"
  1799.             case '\b':
  1800.                 result += "\\b"
  1801.             case _:
  1802.                 if codepoint<32 or codepoint>127:
  1803.                     if len(ch) == 1:
  1804.                         result += "\\u"
  1805.                         result += format(codepoint,'04x')
  1806.                     else:
  1807.                         result += "\\U"
  1808.                         result += format(codepoint,'08x')
  1809.                 else:
  1810.                     result += ch
  1811.         return result
  1812.  
  1813.     @staticmethod
  1814.     def __appendRangeTo(ranges, index, result):
  1815.         first = ranges[index].min
  1816.         last = ranges[index].max
  1817.         if first == 0 and last == 1114111:
  1818.             return result + '.'
  1819.         result = FA.__appendRangeCodepointTo(first, result)
  1820.         if last == first:
  1821.             return result
  1822.        
  1823.         if last == first + 1: # spit out 1 and 2 length ranges as flat chars
  1824.             result = FA.__appendRangeCodepointTo(last, result)
  1825.             return result
  1826.         elif last == first + 2:
  1827.             result = FA.__appendRangeCodepointTo(first+1, result)
  1828.             result = FA.__appendRangeCodepointTo(last, result)
  1829.             return result
  1830.         result +='-'
  1831.         result = FA.__appendRangeCodepointTo(last, result)
  1832.         return result
  1833.  
  1834.     @staticmethod
  1835.     def __appendRangesTo(ranges, result):
  1836.         i = 0
  1837.         while i < len(ranges):
  1838.             result = FA.__appendRangeTo(ranges,i,result)
  1839.             i+= 1
  1840.         return result
  1841.        
  1842.     @staticmethod
  1843.     def __toExprFillEdgesIn(edges, fa, result):
  1844.         for edge in edges:
  1845.             if edge.toState is fa:
  1846.                 result.append(edge)
  1847.     @staticmethod
  1848.     def __toExprFillEdgesOut(edges, fa, result):
  1849.         for edge in edges:
  1850.             if edge.fromState is fa:
  1851.                 result.append(edge)
  1852.     @staticmethod
  1853.     def __toExpressionFillEdgesOrphanState(edges, fa, result):
  1854.         for edge in edges:
  1855.             if ((edge.fromState is fa) or (edge.toState is fa)):
  1856.                 continue
  1857.             result.append(edge)
  1858.     @staticmethod
  1859.     def __toExpressionOrJoin(strings):
  1860.         if len(strings) == 0:
  1861.             return ""
  1862.         if len(strings) == 1:
  1863.             return strings[0]    
  1864.         return "("+"|".join(strings)+")"
  1865.    
  1866.     @staticmethod
  1867.     def __toExpressionKleeneStar(s, noWrap,result):
  1868.         if s is None or len(s) == 0:
  1869.             return result
  1870.         if noWrap == True or len(s) == 1:
  1871.             result += f"{s}*"
  1872.             return result
  1873.         result += f"({s})*"
  1874.         return result
  1875.    
  1876.     @staticmethod
  1877.     def __toExpression(fa: FA) -> str:
  1878.         closure: list[FA] = []
  1879.         fsmEdges: list[_ExpEdge] = []
  1880.         first: FA = fa
  1881.         final: FA = None
  1882.         # linearize the state machine
  1883.         acc: list[FA] = []
  1884.         for f in first.fillClosure():
  1885.             if f.acceptSymbol != -1:
  1886.                 acc.append(f)
  1887.         if len(acc) == 1:
  1888.             final = acc[0]
  1889.         elif len(acc) > 1:
  1890.             fa = fa.clone()
  1891.             first = fa
  1892.             acc.clear()
  1893.             for f in fa.fillClosure():
  1894.                 if f.acceptSymbol != -1:
  1895.                     acc.append(f)
  1896.  
  1897.             final = FA(acc[0].acceptSymbol)
  1898.             for a in acc:
  1899.                 a.addEpsilon(final, False)
  1900.                 a.acceptSymbol = -1
  1901.         first.fillClosure(closure)
  1902.         sb = [] # string builder
  1903.         # build the machine from the FA
  1904.         trnsgrp = dict()
  1905.         for cfa in closure:
  1906.             trnsgrp.clear()
  1907.             for trns in cfa.fillInputTransitionRangesGroupedByState(True,trnsgrp).items():
  1908.                 sb = ""
  1909.                 if len(trns[1]) == 1 and trns[1][0].min == trns[1][0].max:
  1910.                     rng = trns[1][0]
  1911.                     if rng.min == -1 or rng.max == -1:
  1912.                         eedge = _ExpEdge()
  1913.                         eedge.exp = ""
  1914.                         eedge.fromState = cfa
  1915.                         eedge.toState = trns[0]
  1916.                         fsmEdges.append(eedge)
  1917.                         continue
  1918.                     sb = FA.__appendCodepointTo(rng.min, sb)
  1919.                 else:
  1920.                     sb += "["
  1921.                     sb = FA.__appendRangesTo(trns[1], sb)
  1922.                     sb += "]"
  1923.                
  1924.                 edge = _ExpEdge()
  1925.                 edge.exp = sb
  1926.                 edge.fromState = cfa
  1927.                 edge.toState = trns[0]
  1928.                 fsmEdges.append(edge)
  1929.         tmp = FA()
  1930.         tmp.addEpsilon(first, False)
  1931.         q0 = first
  1932.         first = tmp
  1933.         tmp = FA(final.acceptSymbol)
  1934.         qLast = final
  1935.         final.acceptSymbol = -1
  1936.         final.addEpsilon(tmp, False)
  1937.         final = tmp
  1938.         # add first and final
  1939.         newEdge = _ExpEdge()
  1940.         newEdge.exp = ""
  1941.         newEdge.fromState = first
  1942.         newEdge.toState = q0
  1943.         fsmEdges.append(newEdge)
  1944.         newEdge = _ExpEdge()
  1945.         newEdge.exp = ""
  1946.         newEdge.fromState = qLast
  1947.         newEdge.toState = final
  1948.         fsmEdges.append(newEdge)
  1949.         closure[0].setIds()
  1950.         closure.insert(0, first)
  1951.         closure.append(final)
  1952.         inEdges: list[_ExpEdge] = []
  1953.         outEdges: list[_ExpEdge] = []
  1954.         while len(closure) > 2:
  1955.             node = closure[1]
  1956.             loops = []
  1957.             inEdges.clear()
  1958.             FA.__toExprFillEdgesIn(fsmEdges, node, inEdges)
  1959.             for edge in inEdges:
  1960.                 if edge.fromState is edge.toState:
  1961.                     loops.append(edge.exp)
  1962.             sb = ""
  1963.             sb = FA.__toExpressionKleeneStar(FA.__toExpressionOrJoin( loops),len(loops) > 1, sb)
  1964.             middle = sb
  1965.             for inEdge in inEdges:
  1966.                 if inEdge.fromState is inEdge.toState:
  1967.                     continue
  1968.                 outEdges.clear()
  1969.                 FA.__toExprFillEdgesOut(fsmEdges, node, outEdges)
  1970.                 for outEdge in outEdges:
  1971.                     if outEdge.fromState is outEdge.toState:
  1972.                         continue
  1973.                     expEdge = _ExpEdge()
  1974.                     expEdge.fromState = inEdge.fromState
  1975.                     expEdge.toState = outEdge.toState
  1976.                     sb = ""
  1977.                     sb += inEdge.exp
  1978.                     sb += middle
  1979.                     sb += outEdge.exp
  1980.                     expEdge.exp = sb
  1981.                     fsmEdges.append(expEdge)  
  1982.             # reuse inedges since we're not using it
  1983.             inEdges.clear()
  1984.             FA.__toExpressionFillEdgesOrphanState(fsmEdges, node,inEdges)
  1985.             fsmEdges.clear()
  1986.             for edge in inEdges:
  1987.                 fsmEdges.append(edge)
  1988.             closure.remove(node)
  1989.         sb = ""
  1990.         if len(fsmEdges) == 1:
  1991.             return fsmEdges[0].exp
  1992.        
  1993.         if len(fsmEdges) > 1:
  1994.             sb+="("
  1995.             sb+=fsmEdges[0].exp
  1996.             i = 1
  1997.             while i < len(fsmEdges):
  1998.                 sb += "|"
  1999.                 edge = fsmEdges[i]
  2000.                 sb += edge.exp
  2001.                 i += 1
  2002.             sb += ")"
  2003.        
  2004.         return sb
  2005.     def toString(self, format: str | None = "") -> str:
  2006.         if (format is None) or len(format) == 0:
  2007.             return str(self)
  2008.         if format == "e":
  2009.             return FA.__toExpression(self)
  2010.         raise Exception("Invalid format specifier")
  2011.    
  2012.     @staticmethod
  2013.     def __escapeLabel(label: str):
  2014.         if (label is None) or len(label) == 0:
  2015.             return label
  2016.         result = label.replace("\\", "\\\\")
  2017.         result = result.replace("\"", "\\\"")
  2018.         result = result.replace("\n", "\\n")
  2019.         result = result.replace("\r", "\\r")
  2020.         result = result.replace("\0", "\\0")
  2021.         result = result.replace("\v", "\\v")
  2022.         result = result.replace("\t", "\\t")
  2023.         result = result.replace("\f", "\\f")
  2024.         return result
  2025.  
  2026.     @staticmethod
  2027.     def __writeCompoundDotTo(closure: list[FA], writer: list[str], options: FADotGraphOptions | None = None) -> None:
  2028.         writer.append("digraph FA {\n")
  2029.         vert: bool = True
  2030.         if options is None or options.vertical == False:
  2031.             writer.append("rankdir=LR\n")
  2032.             vert = False
  2033.    
  2034.         writer.append("node [shape=circle]\n")
  2035.         opt2 = FADotGraphOptions()
  2036.         opt2.debugSourceNfa = None
  2037.         if options is None:
  2038.             options = FADotGraphOptions()
  2039.         opt2.statePrefix = options.statePrefix
  2040.         opt2.debugString = options.debugString
  2041.         opt2.debugShowNfa = False
  2042.         opt2.dpi = options.dpi
  2043.         opt2.acceptSymbolNames = options.acceptSymbolNames
  2044.         opt2.hideAcceptSymbolIds = options.hideAcceptSymbolIds
  2045.         opt2.blockEnds = options.blockEnds
  2046.         if vert == False:
  2047.             FA.__writeDotTo(closure, writer, options, 2)
  2048.             FA.__writeDotTo(options.debugSourceNfa.fillClosure(), writer, opt2, 1)
  2049.         else:
  2050.             FA.__writeDotTo(options.debugSourceNfa.fillClosure(), writer, opt2, 2)
  2051.             FA.__writeDotTo(closure, writer, options, 1)
  2052.         writer.append("}\n")
  2053.        
  2054.     @staticmethod
  2055.     def __writeDotTo(closure : list[FA], writer : list[str], options : FADotGraphOptions|None = None, clusterIndex : int = -1) -> None:
  2056.         if options is None:
  2057.             options = FADotGraphOptions()
  2058.         hasBlockEnds = options.debugShowNfa == False and (options.debugString is None) and (not (options.blockEnds is None))
  2059.         spfx = "q"
  2060.         if not (options.statePrefix is None) and len(options.statePrefix)>0:
  2061.             spfx = options.statePrefix
  2062.         pfx = ""
  2063.         if clusterIndex != -1:
  2064.             writer.append(f"subgraph cluster_{clusterIndex} {{\n")
  2065.             pfx = f"c{clusterIndex}"
  2066.         else:
  2067.             writer.append("digraph FA {\n")
  2068.         if options.vertical == False:
  2069.             writer.append("rankdir=LR\n");
  2070.        
  2071.         writer.append("node [shape=circle]\n")
  2072.         accepting: list[FA] = []
  2073.         finals: list[FA] = []
  2074.         neutrals: list[FA] = []
  2075.         for ffa in closure:
  2076.             if ffa.acceptSymbol != -1:
  2077.                 accepting.append(ffa)
  2078.             elif ffa.isNeutral():
  2079.                 neutrals.append(ffa)
  2080.             elif ffa.isFinal():
  2081.                 finals.append(ffa)
  2082.  
  2083.         fromStates: list[FA] | None = None
  2084.         toStates: list[FA] | None = None
  2085.  
  2086.         tchar: int = -1
  2087.         if not (options.debugString is None):
  2088.             for ch in FA.toUtf32(options.debugString):
  2089.                 if fromStates is None:
  2090.                     fromStates = closure[0].fillEpsilonClosure()
  2091.                 else:
  2092.                     fromStates = toStates
  2093.                 tchar = ch
  2094.                 toStates = FA.fillMove(fromStates, ch)
  2095.                 if len(toStates) == 0:
  2096.                     break
  2097.         if fromStates is None:
  2098.             fromStates = closure[0].fillEpsilonClosure(fromStates)
  2099.        
  2100.         if not (toStates is None):
  2101.             toStates = FA.fillEpsilonClosureAll(toStates)
  2102.         else:
  2103.             toStates = fromStates
  2104.         i = 0
  2105.         for ffa in closure:
  2106.             isfrom = (not (fromStates is None)) and ffa in FA.fillEpsilonClosureAll(fromStates)
  2107.             rngGrps = ffa.fillInputTransitionRangesGroupedByState()
  2108.             for rngGrp in rngGrps.items():
  2109.                 istrns = isfrom and (not (toStates is None)) and (not (options.debugString is None)) and rngGrp[0] in toStates
  2110.                 di = closure.index(rngGrp[0])
  2111.                 writer.append(f"{pfx}{spfx}{i}->{pfx}{spfx}{di} [label=\"")
  2112.                 sb = ""
  2113.                 notRanges = list(FA.__invertRanges(rngGrp[1]))
  2114.                 if len(notRanges)*1.5 > len(rngGrp[1]):
  2115.                     sb = FA.__appendRangesTo(rngGrp[1],sb)
  2116.                 else:
  2117.                     if len(notRanges) == 0:
  2118.                         sb+="."
  2119.                     else:
  2120.                         sb+="^"
  2121.                         sb = FA.__appendRangesTo(notRanges,sb)
  2122.  
  2123.                 if len(sb) != 1 or sb == " ":
  2124.                     writer.append('[')
  2125.                     if len(sb) > 16:
  2126.                         sb = sb[0:16]
  2127.                         sb += "..."
  2128.                    
  2129.                     writer.append(f"{FA.__escapeLabel(sb)}]")
  2130.                 else:
  2131.                     writer.append(FA.__escapeLabel(sb))
  2132.                 if istrns == False:
  2133.                     writer.append("\"]")
  2134.                 else:
  2135.                     writer.append("\",color=green]\n")
  2136.                
  2137.             # do epsilons
  2138.             for fat in ffa.transitions:
  2139.                 if fat.isEpsilon() == True:
  2140.                     istrns = (not (toStates is None)) and (not (options.debugString is None)) and (ffa in toStates) and fat.to in toStates
  2141.                     col = "gray"
  2142.                     if istrns == True:
  2143.                         col = "green"
  2144.                     writer.append(f"{pfx}{spfx}{i}->{pfx}{spfx}{closure.index(fat.to)} [style=dashed,color={col}]\n")
  2145.    
  2146.             # do block ends
  2147.             bel = 0
  2148.             if not (options.blockEnds is None):
  2149.                 bel = len(options.blockEnds)
  2150.             if hasBlockEnds == True and ffa.acceptSymbol != -1 and bel > ffa.acceptSymbol and (not (options.blockEnds[ffa.acceptSymbol] is None)):
  2151.                 writer.append(f"{pfx}{spfx}{i}->{pfx}blockEnd{ffa.acceptSymbol}{spfx}0 [style=dashed,label=\".*?\"]\n")
  2152.                
  2153.             i += 1
  2154.        
  2155.         delim = ""
  2156.         if hasBlockEnds == True:
  2157.             bel = 0
  2158.             if not (options.blockEnds is None):
  2159.                 bel = len(options.blockEnds)
  2160.             i = 0
  2161.             while i < bel:
  2162.                 bfa = options.blockEnds[i]
  2163.                 if not (bfa is None):
  2164.                     bclose = bfa.fillClosure()
  2165.                     delim = ""
  2166.                     qi = 0
  2167.                     while qi < len(bclose):
  2168.                         cbfa = bclose[qi]
  2169.                         rngGrps = cbfa.fillInputTransitionRangesGroupedByState(True)
  2170.                         for rngGrp in rngGrps.items():
  2171.                             di = bclose.index(rngGrp[0])
  2172.                             writer.append(f"{pfx}blockEnd{i}{spfx}{qi}->{pfx}blockEnd{i}{spfx}{di} [label=\"")
  2173.                             sb = FA.__appendRangesTo(rngGrp[1],sb)
  2174.                             if len(sb) != 1 or sb == " ":
  2175.                                 middle = ""
  2176.                                 if len(sb) > 16:
  2177.                                     sb = sb[:16]
  2178.                                     middle = "..."
  2179.                                 writer.append(f"[{FA.__escapeLabel(sb)}{middle}]\"]\n")
  2180.                             else:
  2181.                                 writer.append(f"{FA.__escapeLabel(sb)}\"]\n")
  2182.                            
  2183.                         # do epsilons
  2184.                         for fat in cbfa.transitions:
  2185.                             if fat.isEpsilon() == True:
  2186.                                 di = bclose.index(fat.to);
  2187.                                 writer.append(f"{pfx}blockEnd{i}{spfx}{qi}->{pfx}blockEnd{i}{spfx}{di} [style=dashed,color=gray]\n")
  2188.                         qi += 1
  2189.                     qi = 0
  2190.                     while qi < len(bclose):
  2191.                         cbfa = bclose[qi]
  2192.                         writer.append(f"{pfx}blockEnd{i}{spfx}{qi} [label=<<TABLE BORDER=\"0\"><TR><TD>(be){spfx}<SUB>{qi}</SUB></TD></TR>")
  2193.                         if cbfa.acceptSymbol!=-1 and options.hideAcceptSymbolIds == False:
  2194.                             acc = None
  2195.                             if (not (options.acceptSymbolNames is None)) and len(options.acceptSymbolNames) > i:
  2196.                                 acc = options.acceptSymbolNames[i]
  2197.                             if acc is None:
  2198.                                 acc = str(i)
  2199.                             acc2 = acc.replace("\"", "&quot;")
  2200.                             writer.append(f"<TR><TD>{acc2}</TD></TR>")
  2201.                            
  2202.                         writer.append("</TABLE>>")
  2203.                         if cbfa.acceptSymbol != -1:
  2204.                             writer.append(",shape=doublecircle")
  2205.                         elif cbfa.isFinal() == True or cbfa.isNeutral() == True:
  2206.                             writer.append(",color=gray")
  2207.                         writer.append("]\n")
  2208.                         qi += 1
  2209.                 i += 1
  2210.         delim = ""
  2211.         i = 0
  2212.         for ffa in closure:
  2213.             col = ""
  2214.             if not (options.debugString is None):
  2215.                 if not (toStates is None):
  2216.                     epstates= FA.fillEpsilonClosureAll(toStates)
  2217.                     if  ffa in epstates:
  2218.                         col = "color=green,"
  2219.                     elif ffa in epstates and (not (ffa in toStates)):
  2220.                         col = "color=darkgreen,"
  2221.                 else:
  2222.                     col = "color=darkgreen,"
  2223.                
  2224.             writer.append(f"{pfx}{spfx}{i} [{col}label=<<TABLE BORDER=\"0\"><TR><TD>{spfx}<SUB>{i}</SUB></TD></TR>")
  2225.            
  2226.             if not (options.debugSourceNfa is None):
  2227.                 fromStates = ffa.__fromStates
  2228.                 if not (fromStates is None):
  2229.                     brk =  int(math.floor(math.sqrt(len(fromStates))))
  2230.                     if len(fromStates) <= 3:
  2231.                         brk = 3
  2232.                     j = 0
  2233.                     while j < len(fromStates):
  2234.                         if j == 0:
  2235.                             writer.append("<TR><TD>{ ")
  2236.                             delim = ""
  2237.                         elif (j % brk) == 0:
  2238.                             delim = ""
  2239.                             writer.append("</TD></TR><TR><TD>")
  2240.                         fromFA = fromStates[j]
  2241.                         writer.append(f"{delim}q<SUB>{options.debugSourceNfa.fillClosure().index(fromFA)}</SUB>")
  2242.                         # putting a comma here is what we'd like
  2243.                         # but it breaks dot no matter how its encoded
  2244.                         delim = " "
  2245.                         if j==len(fromStates)-1:
  2246.                             writer.append(" }</TD></TR>")
  2247.                         j += 1
  2248.                
  2249.             bel = 0
  2250.             if not (options.blockEnds is None):
  2251.                 bel = len(options.blockEnds)
  2252.             if ffa.acceptSymbol != -1 and options.hideAcceptSymbolIds == False and not (hasBlockEnds == True and bel > ffa.acceptSymbol and (not (options.blockEnds[ffa.acceptSymbol] is None))):
  2253.                 acc = None
  2254.                 if not (options.acceptSymbolNames is None) and len(options.acceptSymbolNames) > ffa.acceptSymbol:
  2255.                     acc = options.acceptSymbolNames[ffa.acceptSymbol]
  2256.                 if acc is None:
  2257.                     acc = str(ffa.acceptSymbol)    
  2258.                 acc2 = acc.replace("\"", "&quot;")
  2259.                 writer.append(f"<TR><TD>{acc2}</TD></TR>")
  2260.            
  2261.             writer.append("</TABLE>>")
  2262.             isfinal = False
  2263.  
  2264.             if (ffa in accepting) and ((hasBlockEnds == False or len(bel) <= ffa.acceptSymbol or (options.blockEnds[ffa.acceptSymbol] is None))):
  2265.                 writer.append(",shape=doublecircle")
  2266.             elif isfinal == True or ffa in neutrals:
  2267.                 if ((fromStates is None) or not (ffa in fromStates)) and ((toStates is None) or not (ffa in toStates)):
  2268.                     writer.append(",color=gray")
  2269.            
  2270.             writer.append("]\n")
  2271.             i+=1
  2272.        
  2273.         delim = ""
  2274.         if len(accepting) > 0:
  2275.             for ntfa in accepting:
  2276.                 if hasBlockEnds == False or bel <= ntfa.acceptSymbol or (options.blockEnds is None) or (options.blockEnds[ntfa.acceptSymbol] is None):
  2277.                     writer.append(f"{delim}{pfx}{spfx}{closure.index(ntfa)}")
  2278.                     delim = ","
  2279.             if delim != "":
  2280.                 writer.append(" [shape=doublecircle]\n")
  2281.            
  2282.         delim = ""
  2283.         if len(neutrals) > 0:
  2284.             for ntfa in neutrals:
  2285.                 if ((fromStates is None) or (not (ntfa in fromStates))) and ((toStates is None) or (not (ntfa in toStates))):
  2286.                     writer.append(f"{delim}{pfx}{spfx}{closure.index(ntfa)}")
  2287.                     delim = ","
  2288.                
  2289.             writer.append(" [color=gray]\n")
  2290.             delim = ""
  2291.        
  2292.         delim = ""
  2293.         if len(finals) > 0:
  2294.             for ntfa in finals:
  2295.                 writer.append(f"{delim}{pfx}{spfx}{closure.index(ntfa)}")
  2296.                 delim = ","
  2297.             writer.append(" [shape=circle,color=gray]\n")
  2298.  
  2299.         writer.append("}\n")
  2300.  
  2301.     def __writeDot(self, writer: list[str], options: FADotGraphOptions | None = None) -> None:
  2302.         if (not (options is None)) and (not (options.debugSourceNfa is None)) and options.debugShowNfa == True:
  2303.             FA.__writeCompoundDotTo(self.fillClosure(), writer, options)
  2304.         else:
  2305.             FA.__writeDotTo(self.fillClosure(), writer, options)
  2306.            
  2307.     def renderToFile(self, path: str, options : FADotGraphOptions | None = None) -> None:
  2308.         if options is None:
  2309.             options = FADotGraphOptions()
  2310.         cmd: list[str] = ["dot"]
  2311.         arg: str = "-T"
  2312.         ext: str = os.path.splitext(path)[1].lower()
  2313.         dotwriter: list[str] = []
  2314.         match ext:
  2315.             case ".dot":
  2316.                 self.__writeDot(dotwriter, options)
  2317.                 output = open(path,"w")
  2318.                 output.write("".join(dotwriter))
  2319.                 output.close()
  2320.                 return
  2321.             case ".png":
  2322.                 arg += "png"
  2323.             case ".jpg"|".jpeg":
  2324.                 arg += "jpg"
  2325.             case ".bmp":
  2326.                 arg += "bmp"
  2327.             case ".svg":
  2328.                 arg += "svg"
  2329.             case _:
  2330.                 raise Exception("Unsupported output format")
  2331.         cmd.append(arg)
  2332.         if options.dpi > 0:
  2333.             cmd.append(f"-Gdpi={options.dpi}")
  2334.         self.__writeDot(dotwriter, options)
  2335.         cmd.append(f"-o{path}")
  2336.         p = Popen(cmd, stdout=None, stdin=PIPE, stderr=None, text=True)
  2337.         p.communicate(input="".join(dotwriter))[0]
  2338.  
  2339.        
  2340. #fa = FA.parse("foo\\/(.*)",0)
  2341. # nfa = FA.parse("[A-Z_a-z][0-9A-Z_a-z]*",0,False)
  2342. # opts = FADotGraphOptions()
  2343. # opts.hideAcceptSymbolIds = True
  2344. # opts.debugShowNfa = True
  2345. # opts.debugSourceNfa = nfa
  2346. # opts.dpi = 300
  2347. # opts.vertical = False
  2348. # nfa.toDfa().renderToFile("test.jpg",opts)
  2349.  
  2350. # compact = False
  2351. # firstPart = FA.charset([FARange(ord("A"),ord("Z")),FARange(ord("a"),ord("z")),FARange(ord("_"),ord("_"))],0,compact)
  2352. # nextPart = FA.charset([FARange(ord("A"),ord("Z")),FARange(ord("a"),ord("z")),FARange(ord("_"),ord("_")),FARange(ord("0"),ord("9"))],0,compact)
  2353. # ident = FA.concat([firstPart,FA.repeat(nextPart,0,0,0,compact)],0,compact)
  2354. # print(ident.toArray())
  2355. # print(ident.toDfa().toArray())
  2356. # print(ident.toMinimizedDfa().toArray())
  2357. # print("PARSING ---------------")
  2358. # ident = FA.parse("[A-Za-z_][A-Za-z_0-9]*",0,compact)
  2359. # print(ident.toArray())
  2360. # print(ident.toDfa().toArray())
  2361. # print(ident.toMinimizedDfa().toArray())
  2362.  
Advertisement
Add Comment
Please, Sign In to add comment