# Generated by re2py
# re2py $INPUT -o $OUTPUT -f

from enum import Enum
import os

# Use a small buffer to cover the case when a lexeme doesn't fit.
# In real world use a larger buffer.
BUFSIZE = 10
DEBUG = False

class State:
    def __init__(self, file):
        self.file = file
        self.yyinput = bytearray(BUFSIZE)
        self.yylimit = BUFSIZE - 1 # exclude terminating null
        self.yycursor = self.yylimit
        self.yymarker = self.yylimit
        self.token = self.yylimit
        self.yystate = -1

class Status(Enum):
    END = 0
    READY = 1
    WAITING = 2
    BIG_PACKET = 3
    BAD_PACKET = 4

def fill(st):
    # Error: lexeme too long. In real life could reallocate a larger buffer.
    if st.token < 1:
        return Status.BIG_PACKET

    # Shift buffer contents (discard everything up to the current token).
    st.yyinput = st.yyinput[st.token:st.yylimit]
    st.yycursor -= st.token;
    st.yymarker -= st.token;
    st.yylimit -= st.token;
    st.token = 0;

    # Fill free space at the end of buffer with new data from file.
    bytes = st.file.read(BUFSIZE - st.yylimit - 1) # -1 for sentinel
    if bytes:
        st.yylimit += len(bytes);
        st.yyinput += bytes

    st.yyinput += b'\0' # append sentinel

    return Status.READY

def lex(yyrecord, recv):
    while True:
        yyrecord.token = yyrecord.yycursor
    
        yystate = yyrecord.yystate
        while True:
            match yystate:
                case -1|0:
                    yych = yyrecord.yyinput[yyrecord.yycursor]
                    if yych <= 0x00:
                        if yyrecord.yylimit <= yyrecord.yycursor:
                            yyrecord.yystate = 8
                            return Status.WAITING, recv
                        yyrecord.yycursor += 1
                        yystate = 1
                        continue
                    if yych <= 0x60:
                        yyrecord.yycursor += 1
                        yystate = 1
                        continue
                    if yych <= 0x7A:
                        yyrecord.yycursor += 1
                        yystate = 3
                        continue
                    yyrecord.yycursor += 1
                    yystate = 1
                    continue
                case 1:
                    yystate = 2
                    continue
                case 2:
                    yyrecord.yystate = -1
                    return Status.BAD_PACKET, recv
                case 3:
                    yyrecord.yymarker = yyrecord.yycursor
                    yych = yyrecord.yyinput[yyrecord.yycursor]
                    if yych <= 0x3B:
                        if yych <= 0x00:
                            if yyrecord.yylimit <= yyrecord.yycursor:
                                yyrecord.yystate = 9
                                return Status.WAITING, recv
                            yystate = 2
                            continue
                        if yych <= 0x3A:
                            yystate = 2
                            continue
                        yyrecord.yycursor += 1
                        yystate = 4
                        continue
                    else:
                        if yych <= 0x60:
                            yystate = 2
                            continue
                        if yych <= 0x7A:
                            yyrecord.yycursor += 1
                            yystate = 5
                            continue
                        yystate = 2
                        continue
                case 4:
                    yyrecord.yystate = -1
                    recv += 1
                    break
                case 5:
                    yych = yyrecord.yyinput[yyrecord.yycursor]
                    if yych <= 0x3B:
                        if yych <= 0x00:
                            if yyrecord.yylimit <= yyrecord.yycursor:
                                yyrecord.yystate = 10
                                return Status.WAITING, recv
                            yystate = 6
                            continue
                        if yych >= 0x3B:
                            yyrecord.yycursor += 1
                            yystate = 4
                            continue
                        yystate = 6
                        continue
                    else:
                        if yych <= 0x60:
                            yystate = 6
                            continue
                        if yych <= 0x7A:
                            yyrecord.yycursor += 1
                            yystate = 5
                            continue
                        yystate = 6
                        continue
                case 6:
                    yyrecord.yycursor = yyrecord.yymarker
                    yystate = 2
                    continue
                case 7:
                    yyrecord.yystate = -1
                    return Status.END, recv
                case 8:
                    if yyrecord.yylimit <= yyrecord.yycursor:
                        yystate = 7
                        continue
                    yystate = 0
                    continue
                case 9:
                    if yyrecord.yylimit <= yyrecord.yycursor:
                        yystate = 2
                        continue
                    yystate = 3
                    continue
                case 10:
                    if yyrecord.yylimit <= yyrecord.yycursor:
                        yystate = 6
                        continue
                    yystate = 5
                    continue
                case _:
                    raise "internal lexer error"


def test(packets, expect):
    # Create a pipe (open the same file for reading and writing).
    fname = "pipe"
    fw = open(fname, "wb")
    fr = open(fname, "rb")

    # Initialize lexer state
    st = State(fr)

    # Main loop. The buffer contains incomplete data which appears packet by
    # packet. When the lexer needs more input it saves its internal state and
    # returns to the caller which should provide more input and resume lexing.
    send = 0
    recv = 0
    while True:
        status, recv = lex(st, recv)

        if status == Status.END:
            if DEBUG: print("done: got {} packets".format(recv))
            break

        elif status == Status.WAITING:
            if DEBUG: print("waiting...");

            if send < len(packets):
                if DEBUG: print("sent packet {}: {}".format(send, packets[send]))
                fw.write(packets[send])
                fw.flush()
                send += 1

            status = fill(st)
            if DEBUG: print("queue: '{}', status: {}".format(st.yyinput, status))
            if status == Status.BIG_PACKET:
                if DEBUG: print("error: packet too big")
                break

            assert status == Status.READY

        else:
            assert status == Status.BAD_PACKET
            if DEBUG: print("error: ill-formed packet")
            break

    # Check results.
    assert status == expect
    if status == Status.END:
        assert recv == send

    # Cleanup: remove input file.
    fr.close()
    fw.close()
    os.remove(fname)

def main():
    test([], Status.END)
    test([b"zero;", b"one;", b"two;", b"three;", b"four;"], Status.END)
    test([b"zer0;"], Status.BAD_PACKET)
    test([b"goooooooooogle;"], Status.BIG_PACKET)

if __name__ == '__main__':
    main()
