Simple text parser using regexes I'm trying to write simple parser using regexes. This is what I currently have, it looks really messy. Any tips what can I change?
from re import compile
from typing import Dict, Iterator, List, NamedTuple, TextIO


# PATTERNS
registers_pattern = list(map(compile, [
  r'(rax=[0-9a-f]{16}) (rbx=[0-9a-f]{16}) (rcx=[0-9a-f]{16})\n',
  r'(rdx=[0-9a-f]{16}) (rsi=[0-9a-f]{16}) (rdi=[0-9a-f]{16})\n',
  r'(rip=[0-9a-f]{16}) (rsp=[0-9a-f]{16}) (rbp=[0-9a-f]{16})\n',
  r' (r8=[0-9a-f]{16})  (r9=[0-9a-f]{16}) (r10=[0-9a-f]{16})\n',
  r'(r11=[0-9a-f]{16}) (r12=[0-9a-f]{16}) (r13=[0-9a-f]{16})\n',
  r'(r14=[0-9a-f]{16}) (r15=[0-9a-f]{16})\n',
]))

flags_pattern = compile(r'iopl=[0-9a-f]+(?:\s+[a-z]{2}){8}\n')
segments_pattern = compile(r'(?:[a-z]{2}=[0-9a-f]{4}\s+){6}efl=[0-9a-f]{8}\n')
label_pattern = compile(r'[\w\+]+:\n')

instruction_pattern = compile(
  r'[0-9a-f]{8}`[0-9a-f]{8}\s+(?P&lt;ENCODING&gt;[0-9a-f]+)\s+(?P&lt;INSTRUCTION&gt;.+?)\n?'
)


class Instruction(NamedTuple):
  instruction: str
  encoding: str
  registers: Dict[str, str]

def parse_trace(stream: TextIO) -&gt; Iterator[Instruction]:
  &quot;&quot;&quot; TODO: some description
  &quot;&quot;&quot;

  iterator = iter(enumerate(stream, start=1))
  for index, line in iterator:
    # Parse general-purpose registers
    registers: Dict[str, str] = {}
  
    for pattern in registers_pattern:
      if match := pattern.fullmatch(line):
        # Extract register values from regex match and go to the next line
        registers.update(group.split('=') for group in match.groups())
        index, line = next(iterator)
      else:
        raise RuntimeError(f'Failed to parse line: {index}')


    if flags_pattern.fullmatch(line) is None:
      raise RuntimeError(f'Failed to parse line: {index}')

    if segments_pattern.fullmatch(next(iterator)[1]) is None:
      # TODO: here will be something
      raise RuntimeError(f'Failed to parse line: {index}')

    if label_pattern.fullmatch(next(iterator)[1]) is None:
      raise RuntimeError(f'Failed to parse line: {index}')
    
    if (match := instruction_pattern.fullmatch(next(iterator)[1])) is None:
      raise RuntimeError(f'Failed to parse line: {index}')
    
    yield Instruction(match.group('INSTRUCTION'), match.group('ENCODING'), registers)


# Example of usage:
from io import StringIO

trace = StringIO(&quot;&quot;&quot;rax=0000000000000000 rbx=0000000000000000 rcx=0000000000000000
rdx=0000000000000000 rsi=0000000000000000 rdi=0000000000000000
rip=000000000040100a rsp=0000000000000000 rbp=0000000000000000
 r8=0000000000000000  r9=0000000000000000 r10=0000000000000000
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
lol+0x1000:
00000000`00401000 48bba47868302035e80c mov rbx,0CE83520306878A4h
rax=0000000000000000 rbx=0000000000000000 rcx=0000000000000000
rdx=0000000000000000 rsi=0000000000000000 rdi=0000000000000000
rip=000000000040100a rsp=0000000000000000 rbp=0000000000000000
 r8=0000000000000000  r9=0000000000000000 r10=0000000000000000
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
lol+0x1000:
00000000`00401000 48bba47868302035e80c mov rbx,0CE83520306878A4h&quot;&quot;&quot;)

for each in parse_trace(trace):
  print(each.instruction)
```
Instead of trying to match whole lines, it would be simpler to match smaller chunks of the input and the combine the smaller regexes.  For example, one regex could match any of the registers, a different one could match the segment registers, etc.  Order of the patterns may be important if one regex is be a prefix of another, but that doesn't seem to apply in this case.
Use capture groups to get the important information, and MatchObject.lastgroup to see which part of the regex matched.
The flags pattern probably needs to have other codes added.
The (?x) in the pattern is for verbose mode, which lets you add whitespace and comments to the regex pattern.
pattern = r&quot;&quot;&quot;(?x)
     (?P&lt;REG&gt;r\w{1,2})=(?P&lt;RVAL&gt;[0-9a-f]{16})            # registers
    |iopl=(?P&lt;IOPL&gt;[0-9a-f]+)                            # iopl
    |(?P&lt;FLAGS&gt;(\s+(?:nv|up|ei|pl|zr|na|po|nc))+)        # flags
    |(?P&lt;SREG&gt;[csdefg]s|efl)=(?P&lt;SVAL&gt;[0-9a-f]+)         # segments
    |(?P&lt;LABEL&gt;^[\w\+]+:)                                # label
    |[0-9a-f]{8}`[0-9a-f]{8}\s+(?P&lt;ENCODING&gt;[0-9a-f]+)\s+(?P&lt;INSTRUCTION&gt;.+)
    |(?P&lt;NL&gt;\n)
    &quot;&quot;&quot;
regex = re.compile(pattern)

registers = {}

lineno = 1

for line in trace:
    print(f&quot;line = {lineno}&quot;)
    
    for mo in regex.finditer(line):
        group_name = mo.lastgroup
        
        if group_name == 'NL':
            lineno += 1
            
        else:
            if group_name == &quot;RVAL&quot;:
                registers[mo['REG']] = mo['RVAL']
                print(f&quot;   {mo['REG']} = {mo['RVAL']}&quot;)
                
            elif group_name == &quot;IOPL&quot;:
                print(f&quot;   iopl = {mo['IOPL']}&quot;)                
                
            elif group_name == &quot;FLAGS&quot;:
                print(f&quot;   FLAGS = {mo['FLAGS'].strip().split()}&quot;)                
                
            elif group_name == &quot;SVAL&quot;:
                print(f&quot;   {mo['SREG']} = {mo['SVAL']}&quot;)
                
            elif group_name == &quot;LABEL&quot;:
                print(f&quot;   LABEL = {mo['LABEL']}&quot;)                
                
            elif group_name == &quot;INSTRUCTION&quot;:
                print(f&quot;   {mo['INSTRUCTION']} = {mo['ENCODING']}&quot;)

For the sample input, the code outputs:
line = 1
   rax = 0000000000000000
   rbx = 0000000000000000
   rcx = 0000000000000000
line = 2
   rdx = 0000000000000000
   rsi = 0000000000000000
   rdi = 0000000000000000
line = 3
   rip = 000000000040100a
   rsp = 0000000000000000
   rbp = 0000000000000000
line = 4
   r8 = 0000000000000000
   r9 = 0000000000000000
   r10 = 0000000000000000
line = 5
   r11 = 0000000000000000
   r12 = 0000000000000000
   r13 = 0000000000000000
line = 6
   r14 = 0000000000000000
   r15 = 0000000000000000
line = 7
   iopl = 0
   FLAGS = ['nv', 'up', 'ei', 'pl', 'zr', 'na', 'po', 'nc']
line = 8
   cs = 0033
   ss = 002b
   ds = 002b
   es = 002b
   fs = 0053
   gs = 002b
   efl = 00000246
line = 9
   LABEL = lol+0x1000:
line = 10
   mov rbx,0CE83520306878A4h = 48bba47868302035e80c
line = 11
   rax = 0000000000000000
   rbx = 0000000000000000
   rcx = 0000000000000000
line = 12
   rdx = 0000000000000000
   rsi = 0000000000000000
   rdi = 0000000000000000
line = 13
   rip = 000000000040100a
   rsp = 0000000000000000
   rbp = 0000000000000000
line = 14
   r8 = 0000000000000000
   r9 = 0000000000000000
   r10 = 0000000000000000
line = 15
   r11 = 0000000000000000
   r12 = 0000000000000000
   r13 = 0000000000000000
line = 16
   r14 = 0000000000000000
   r15 = 0000000000000000
line = 17
   iopl = 0
   FLAGS = ['nv', 'up', 'ei', 'pl', 'zr', 'na', 'po', 'nc']
line = 18
   cs = 0033
   ss = 002b
   ds = 002b
   es = 002b
   fs = 0053
   gs = 002b
   efl = 00000246
line = 19
   LABEL = lol+0x1000:
line = 20
   mov rbx,0CE83520306878A4h = 48bba47868302035e80c

Obviously, do something useful instead of just printing the information.