# Generated by lexgen_code.py from lexgen JSON tables. Do not edit.
from __future__ import annotations

from typing import Optional

from runtime.token import Token, Location, new_eof_token, new_error_token, new_token


START_STATE = {{ start_state }}

TRANSITIONS: dict[int, list[tuple[int, int, int]]] = {
{% for t in transitions %}
    {{ t.state }}: [
{% for r in t.ranges %}
        ({{ r["from"] }}, {{ r["to"] }}, {{ r["next"] }}),
{% endfor %}
    ],
{% endfor %}
}

ACTIONS: dict[int, str] = {
{% for a in actions %}
    {{ a.state }}: {{ a.token_type|repr }},
{% endfor %}
}


def _lookup_transition(state: int, r: int) -> Optional[int]:
    ranges = TRANSITIONS.get(state, [])
    for from_, to_, next_ in ranges:
        if r < from_:
            return None
        if from_ <= r <= to_:
            return next_
    return None


def _is_ignored_token(token_type: str) -> bool:
    return token_type.startswith("!")


class {{ class_name }}:
    """Generated lexer implementing AbstractLexer protocol."""

    def __init__(self, input_text: str) -> None:
        self._input = input_text
        self._length = len(input_text)
        self._index = 0
        self._line = 1
        self._column = 1

    def scan(self) -> Optional[Token]:
        while True:
            if self._index >= self._length:
                return new_eof_token(Location(line=self._line, column=self._column, byte_offset=self._index))

            start_line, start_col = self._line, self._column
            start_index = self._index
            state = START_STATE
            last_accept_state: Optional[int] = None
            last_accept_index = self._index
            last_accept_line, last_accept_col = self._line, self._column

            while self._index < self._length:
                r = ord(self._input[self._index])
                next_state = _lookup_transition(state, r)
                if next_state is None:
                    break
                self._index += 1
                if self._input[self._index - 1] == "\n":
                    self._line += 1
                    self._column = 1
                else:
                    self._column += 1
                state = next_state
                if state in ACTIONS:
                    last_accept_state = state
                    last_accept_index = self._index
                    last_accept_line, last_accept_col = self._line, self._column

            if last_accept_state is None:
                r = ord(self._input[self._index]) if self._index < self._length else 0
                return new_error_token(
                    f"lexer: unrecognized input {chr(r)!r}",
                    Location(line=self._line, column=self._column, byte_offset=self._index),
                )

            lexeme = self._input[start_index:last_accept_index]
            self._index = last_accept_index
            self._line, self._column = last_accept_line, last_accept_col
            token_type = ACTIONS[last_accept_state]
{% if has_ignored %}
            if _is_ignored_token(token_type):
                continue
{% endif %}
            return new_token(lexeme, token_type, Location(line=start_line, column=start_col, byte_offset=start_index))
