from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, TypeVar
from enum import Enum

from xdsl.ir import (SSAValue, Block, Callable, Attribute, Operation, Region,
                     BlockArgument, MLContext, ParametrizedAttribute)

from xdsl.dialects.builtin import (
    AnyFloat, AnyTensorType, AnyUnrankedTensorType, AnyVectorType,
    DenseIntOrFPElementsAttr, Float16Type, Float32Type, Float64Type, FloatAttr,
    FunctionType, IndexType, IntegerType, OpaqueAttr, Signedness, StringAttr,
    FlatSymbolRefAttr, IntegerAttr, ArrayAttr, TensorType, UnitAttr,
    UnrankedTensorType, UnregisteredOp, VectorType)
from xdsl.irdl import Data

indentNumSpaces = 2


@dataclass(frozen=True)
class Position:
    """A position in a file"""

    file: str
    """
    A handle to the file contents. The position is relative to this file.
    """

    idx: int = field(default=0)
    """
    The character index in the entire file.
    A line break is consider to be a character here.
    """

    line: int = field(default=1)
    """The line index."""

    column: int = field(default=1)
    """The character index in the current line."""

    def __str__(self):
        return f"{self.line}:{self.column}"

    def next_char_pos(self, n: int = 1) -> Position | None:
        """Return the position of the next character in the string."""
        if self.idx >= len(self.file) - n:
            return None
        new_idx = self.idx
        new_line = self.line
        new_column = self.column
        while n > 0:
            if self.file[new_idx] == '\n':
                new_line += 1
                new_column = 1
            else:
                new_column += 1
            new_idx += 1
            n -= 1
        assert new_idx < len(self.file)
        return Position(self.file, new_idx, new_line, new_column)

    def get_char(self) -> str:
        """Return the character at the current position."""
        assert self.idx < len(self.file)
        return self.file[self.idx]

    def get_current_line(self) -> str:
        """Return the current line."""
        assert self.idx < len(self.file)
        start_idx = self.idx - self.column + 1
        end_idx = self.idx
        while self.file[end_idx] != '\n':
            end_idx += 1
        return self.file[start_idx:end_idx]


@dataclass
class ParserError(Exception):
    """An error triggered during parsing."""

    pos: Position | None
    message: str

    def __str__(self):
        if self.pos is None:
            return f"Parsing error at end of file :{self.message}\n"
        message = f"Parsing error at {self.pos}:\n"
        message += self.pos.get_current_line() + '\n'
        message += " " * (self.pos.column - 1) + "^\n"
        message += self.message + '\n'
        return message


@dataclass
class Parser:

    class Source(Enum):
        XDSL = 1
        MLIR = 2

    ctx: MLContext
    """xDSL context."""

    str: str
    """The current file/input to parse."""

    source: Source = field(default=Source.XDSL, kw_only=True)
    """The source language to parse."""

    allow_unregistered_ops: bool = field(default=False, kw_only=True)
    """Allow the parsing of unregistered ops."""

    _pos: Position | None = field(init=False)
    """Position in the file. None represent the end of the file."""

    _ssaValues: dict[str, SSAValue] = field(init=False, default_factory=dict)
    """Associate SSA values with their names."""

    _blocks: dict[str, Block] = field(init=False, default_factory=dict)
    """Associate blocks with their names."""

    def __post_init__(self):
        if len(self.str) == 0:
            self._pos = None
        else:
            self._pos = Position(self.str)

    def get_pos(self) -> Position | None:
        """Return the current position."""
        return self._pos

    def get_char(self,
                 n: int = 1,
                 skip_white_space: bool = True) -> str | None:
        """Get the next n characters (including the current one)"""
        assert n >= 0
        if skip_white_space:
            self.skip_white_space()
        if self._pos is None:
            return None
        if self._pos.idx + n >= len(self.str):
            return None
        return self.str[self._pos.idx:self._pos.idx + n]

    _T = TypeVar("_T")

    def try_parse(self,
                  parse_fn: Callable[[], _T | None],
                  skip_white_space: bool = True) -> _T | None:
        """
        Wrap a parsing function. If the parsing fails, then return without
        any change to the current position.
        """
        if skip_white_space:
            self.skip_white_space()
        start_pos = self._pos
        try:
            return parse_fn()
        except ParserError:
            pass
        self._pos = start_pos
        return None

    def skip_white_space(self) -> None:
        while pos := self._pos:
            char = pos.get_char()
            if char.isspace():
                self._pos = pos.next_char_pos()
            elif self.get_char(2, skip_white_space=False) == "//":
                self.parse_while(lambda x: x != '\n', False)
            else:
                return

    def parse_while(self,
                    cond: Callable[[str], bool],
                    skip_white_space: bool = True) -> str:
        if skip_white_space:
            self.skip_white_space()
        start_pos = self._pos
        if start_pos is None:
            return ""
        while self._pos:
            char = self._pos.get_char()
            if not cond(char):
                return self.str[start_pos.idx:self._pos.idx]
            self._pos = self._pos.next_char_pos()
        return self.str[start_pos.idx:]

    # TODO why two different functions, no nums in ident?
    def parse_optional_ident(self,
                             skip_white_space: bool = True) -> str | None:
        res = self.parse_while(lambda x: x.isalpha() or x == "_" or x == ".",
                               skip_white_space=skip_white_space)
        if len(res) == 0:
            return None
        return res

    def parse_ident(self, skip_white_space: bool = True) -> str:
        res = self.parse_optional_ident(skip_white_space=skip_white_space)
        if res is None:
            raise ParserError(self._pos, "ident expected")
        return res

    def parse_optional_alpha_num(self,
                                 skip_white_space: bool = True) -> str | None:
        res = self.parse_while(lambda x: x.isalnum() or x == "_" or x == ".",
                               skip_white_space=skip_white_space)
        if len(res) == 0:
            return None
        return res

    def parse_alpha_num(self, skip_white_space: bool = True) -> str:
        res = self.parse_optional_alpha_num(skip_white_space=skip_white_space)
        if res is None:
            raise ParserError(self._pos, "alphanum expected")
        return res

    def parse_optional_str_literal(self,
                                   skip_white_space: bool = True
                                   ) -> str | None:
        parsed = self.parse_optional_char('"',
                                          skip_white_space=skip_white_space)
        if parsed is None:
            return None
        start_pos = self._pos
        if start_pos is None:
            raise ParserError(None, "Unexpected end of file")
        while self._pos:
            pos = self._pos
            char = pos.get_char()
            if char == '\\':
                if next_pos := pos.next_char_pos():
                    escaped = next_pos.get_char()
                    if escaped in ['\\', 'n', 't', 'r', '"']:
                        self._pos = next_pos.next_char_pos()
                        continue
                    else:
                        raise ParserError(
                            next_pos,
                            f"Unrecognized escaped character: \\{escaped}")
                else:
                    raise ParserError(None, "Unexpected end of file")
            elif char == '"':
                break
            self._pos = pos.next_char_pos()
        if self._pos is None:
            res = self.str[start_pos.idx:]
        else:
            res = self.str[start_pos.idx:self._pos.idx]
        self.parse_char('"')
        return res

    def parse_str_literal(self, skip_white_space: bool = True) -> str:
        res = self.parse_optional_str_literal(
            skip_white_space=skip_white_space)
        if res is None:
            raise ParserError(self._pos, "string literal expected")
        return res

    def parse_optional_int_literal(self,
                                   skip_white_space: bool = True
                                   ) -> int | None:
        is_negative = self.parse_optional_char(
            "-", skip_white_space=skip_white_space)
        res = self.parse_while(lambda char: char.isnumeric(),
                               skip_white_space=False)
        if len(res) == 0:
            if is_negative:
                raise ParserError(self._pos, "int literal expected")
            return None
        return int(res) if is_negative is None else -int(res)

    def parse_int_literal(self, skip_white_space: bool = True) -> int:
        res = self.parse_optional_int_literal(
            skip_white_space=skip_white_space)
        if res is None:
            raise ParserError(self._pos, "int literal expected")
        return res

    def parse_optional_float_literal(self,
                                     skip_white_space: bool = True
                                     ) -> float | None:
        return self.try_parse(self.parse_float_literal,
                              skip_white_space=skip_white_space)

    def parse_float_literal(self, skip_white_space: bool = True) -> float:
        # Parse the optional sign
        value = ""
        if self.parse_optional_char("+", skip_white_space=skip_white_space):
            value += "+"
        elif self.parse_optional_char("-", skip_white_space=False):
            value += "-"

        # Parse the significant digits
        digits = self.parse_while(lambda x: x.isdigit(),
                                  skip_white_space=False)
        if digits == "":
            raise ParserError(self._pos, "float literal expected")
        value += digits

        # Check that we are parsing a float, and not an integer
        is_float = False

        # Parse the optional decimal point
        if self.parse_optional_char(".", skip_white_space=False):
            # Parse the fractional digits
            value += "."
            value += self.parse_while(lambda x: x.isdigit(),
                                      skip_white_space=False)
            is_float = True

        # Parse the optional exponent
        if self.parse_optional_char(
                "e", skip_white_space=False) or self.parse_optional_char(
                    "E", skip_white_space=False):
            value += "e"
            # Parse the optional exponent sign
            if self.parse_optional_char("+", skip_white_space=False):
                value += "+"
            elif self.parse_optional_char("-", skip_white_space=False):
                value += "-"
            # Parse the exponent digits
            value += self.parse_while(lambda x: x.isdigit(),
                                      skip_white_space=False)
            is_float = True

        if not is_float:
            raise ParserError(
                self._pos,
                "float literal expected, but got an integer literal instead")

        return float(value)

    def peek_char(self,
                  char: str,
                  skip_white_space: bool = True) -> bool | None:
        if skip_white_space:
            self.skip_white_space()
        if self.get_char() == char:
            return True
        return None

    def parse_optional_char(self,
                            char: str,
                            skip_white_space: bool = True) -> bool | None:
        assert len(char) == 1
        if skip_white_space:
            self.skip_white_space()
        if self._pos is None:
            return None
        if self._pos.get_char() == char:
            self._pos = self._pos.next_char_pos()
            return True
        return None

    def parse_char(self, char: str, skip_white_space: bool = True) -> bool:
        assert (len(char) == 1)
        res = self.parse_optional_char(char, skip_white_space=skip_white_space)
        if res is None:
            raise ParserError(self._pos, f"'{char}' expected")
        return True

    def parse_string(self,
                     contents: str,
                     skip_white_space: bool = True) -> bool:
        if skip_white_space:
            self.skip_white_space()
        chars = self.get_char(len(contents))
        if chars == contents:
            assert self._pos
            self._pos = self._pos.next_char_pos(len(contents))
            return True
        raise ParserError(self._pos, f"'{contents}' expected")

    def parse_optional_string(self,
                              contents: str,
                              skip_white_space: bool = True) -> bool | None:
        if skip_white_space:
            self.skip_white_space()
        chars = self.get_char(len(contents))
        if chars == contents:
            assert self._pos is not None
            self._pos = self._pos.next_char_pos(len(contents))
            return True
        return None

    T = TypeVar('T')

    def parse_optional_nested_list(
            self,
            parse_optional_one: Callable[[], T | None],
            delimiter: str = ",",
            brackets: str = "[]",
            skip_white_space: bool = True) -> list[T] | None:
        '''
        Parse and flatten a list of lists. The result is a list of elements, no matter the
        rank of the input.
        Delimiter must be length one, for example ",".
        Brackets must be length two, for example "[]".
        '''

        assert len(delimiter) == 1
        assert len(brackets) == 2

        open_bracket, close_bracket = brackets
        if not self.parse_optional_char(open_bracket,
                                        skip_white_space=skip_white_space):
            # This is not a list that opens with the opening bracket
            return None

        indices = [0]

        res = list[Any]()  # Pyright does not let us use `T` here

        while len(indices) > 0:
            if self.parse_optional_char(close_bracket,
                                        skip_white_space=skip_white_space):
                # This is the end of a list
                indices.pop()
                if len(indices) > 0:
                    indices[-1] += 1
                continue

            if indices[-1]:
                # If we're not at the end of the list, then it's a delimiter followed by
                # the next eleement, which might be a nested list.
                self.parse_char(delimiter, skip_white_space=skip_white_space)

            if self.parse_optional_char(open_bracket,
                                        skip_white_space=skip_white_space):
                # A new nested list, reset the index
                indices.append(0)
            else:
                # This must be a list element
                one = parse_optional_one()
                if one is None:
                    raise ParserError(self._pos, 'Expected list element')
                res.append(one)
                indices[-1] += 1

        return res

    def parse_list(self,
                   parse_optional_one: Callable[[], T | None],
                   delimiter: str = ",",
                   skip_white_space: bool = True) -> list[T]:
        if skip_white_space:
            self.skip_white_space()
        assert (len(delimiter) <= 1)
        res = list[Any]()  # Pyright do not let us use `T` here
        one = parse_optional_one()
        if one is not None:
            res.append(one)
        while self.parse_optional_char(delimiter) if len(
                delimiter) == 1 else True:
            one = parse_optional_one()
            if one is None:
                return res
            res.append(one)
        return res

    def parse_optional_block_argument(
            self,
            skip_white_space: bool = True) -> tuple[str, Attribute] | None:
        name = self.parse_optional_ssa_name(skip_white_space=skip_white_space)
        if name is None:
            return None
        self.parse_char(":")
        typ = self.parse_attribute()
        # TODO how to get the id?
        return name, typ

    def parse_optional_named_block(self,
                                   skip_white_space: bool = True
                                   ) -> Block | None:
        if self.parse_optional_char("^",
                                    skip_white_space=skip_white_space) is None:
            return None
        block_name = self.parse_alpha_num(skip_white_space=False)
        if block_name in self._blocks:
            block = self._blocks[block_name]
        else:
            block = Block()
            self._blocks[block_name] = block

        if self.parse_optional_char("("):
            tuple_list = self.parse_list(self.parse_optional_block_argument)
            # Register the BlockArguments as ssa values and add them to
            # the block
            for (idx, (arg_name, arg_type)) in enumerate(tuple_list):
                if arg_name in self._ssaValues:
                    raise ParserError(
                        self._pos, f"SSA value {arg_name} is already defined")
                arg = BlockArgument(arg_type, block, idx)
                self._ssaValues[arg_name] = arg
                block.args.append(arg)

            self.parse_char(")")
        self.parse_char(":")
        for op in self.parse_list(self.parse_optional_op, delimiter=""):
            block.add_op(op)
        return block

    def parse_optional_region(self,
                              skip_white_space: bool = True) -> Region | None:
        if not self.parse_optional_char("{",
                                        skip_white_space=skip_white_space):
            return None
        region = Region()
        oldSSAVals = self._ssaValues.copy()
        oldBBNames = self._blocks.copy()
        self._blocks = dict[str, Block]()

        if self.peek_char('^'):
            for block in self.parse_list(self.parse_optional_named_block,
                                         delimiter=""):
                region.add_block(block)
        else:
            region.add_block(Block())
            for op in self.parse_list(self.parse_optional_op, delimiter=""):
                region.blocks[0].add_op(op)
        self.parse_char("}")

        self._ssaValues = oldSSAVals
        self._blocks = oldBBNames
        return region

    def parse_optional_ssa_name(self,
                                skip_white_space: bool = True) -> str | None:
        if self.parse_optional_char("%",
                                    skip_white_space=skip_white_space) is None:
            return None
        name = self.parse_alpha_num()
        return name

    def parse_optional_ssa_value(self,
                                 skip_white_space: bool = True
                                 ) -> SSAValue | None:
        if skip_white_space:
            self.skip_white_space()
        start_pos = self._pos
        name = self.parse_optional_ssa_name()
        if name is None:
            return None
        if name not in self._ssaValues:
            raise ParserError(start_pos,
                              f"name {name} does not refer to a SSA value")
        return self._ssaValues[name]

    def parse_ssa_value(self, skip_white_space: bool = True) -> SSAValue:
        res = self.parse_optional_ssa_value(skip_white_space=skip_white_space)
        if res is None:
            raise ParserError(self._pos, "SSA value expected")
        return res

    def parse_optional_results(self,
                               skip_white_space: bool = True
                               ) -> list[str] | None:
        res = self.parse_list(self.parse_optional_ssa_name,
                              skip_white_space=skip_white_space)
        if len(res) == 0:
            return None
        self.parse_char("=")
        return res

    def parse_optional_typed_result(
            self,
            skip_white_space: bool = True) -> tuple[str, Attribute] | None:
        name = self.parse_optional_ssa_name(skip_white_space=skip_white_space)
        if name is None:
            return None
        self.parse_char(":")
        typ = self.parse_attribute()
        return name, typ

    def parse_optional_typed_results(
            self,
            skip_white_space: bool = True
    ) -> list[tuple[str, Attribute]] | None:
        res = self.parse_list(lambda: self.parse_optional_typed_result(
            skip_white_space=skip_white_space))
        if len(res) == 0:
            return None
        elif len(res) == 1 and res[0] is None:
            return None
        else:
            self.parse_char("=")
            return res

    def parse_optional_operand(self,
                               skip_white_space: bool = True
                               ) -> SSAValue | None:
        value = self.parse_optional_ssa_value(
            skip_white_space=skip_white_space)
        if value is None:
            return None
        if self.source == self.Source.XDSL:
            self.parse_char(":")
            typ = self.parse_attribute()
            if value.typ != typ:
                raise ParserError(
                    self._pos, f"type mismatch between {typ} and {value.typ}")
        return value

    def parse_operands(self, skip_white_space: bool = True) -> list[SSAValue]:
        self.parse_char("(", skip_white_space=skip_white_space)
        res = self.parse_list(lambda: self.parse_optional_operand())
        self.parse_char(")")
        return res

    def parse_paramattr_parameters(
            self,
            expect_brackets: bool = False,
            skip_white_space: bool = True) -> list[Attribute]:
        if expect_brackets:
            self.parse_char("<", skip_white_space=skip_white_space)
        elif self.parse_optional_char(
                "<", skip_white_space=skip_white_space) is None:
            return []

        res = self.parse_list(self.parse_optional_attribute)
        self.parse_char(">")
        return res

    def parse_optional_boolean_attribute(
            self,
            skip_white_space: bool = True) -> IntegerAttr[IntegerType] | None:
        if self.parse_optional_string(
                "true", skip_white_space=skip_white_space) is not None:
            return IntegerAttr.from_int_and_width(1, 1)
        if self.parse_optional_string(
                "false", skip_white_space=skip_white_space) is not None:
            return IntegerAttr.from_int_and_width(0, 1)

    def parse_optional_xdsl_builtin_attribute(self,
                                              skip_white_space: bool = True
                                              ) -> Attribute | None:
        # Shorthand for StringAttr
        string_lit = self.parse_optional_str_literal(
            skip_white_space=skip_white_space)
        if string_lit is not None:
            return StringAttr.from_str(string_lit)

        # Shorthand for FloatAttr
        float_lit = self.parse_optional_float_literal()
        if float_lit is not None:
            if self.parse_optional_char(":"):
                typ = self.parse_attribute()
            else:
                typ = Float32Type()
            return FloatAttr.from_value(float_lit, typ)

        # Shorthand for boolean literals (IntegerAttr of width 1)
        if (bool_attr := self.parse_optional_boolean_attribute(
                skip_white_space=skip_white_space)):
            return bool_attr

        # Shorthand for IntegerAttr
        integer_lit = self.parse_optional_int_literal()
        if integer_lit is not None:
            if self.parse_optional_char(":"):
                typ = self.parse_attribute()
            else:
                typ = IntegerType.from_width(64)
            return IntegerAttr.from_params(integer_lit, typ)

        # Shorthand for ArrayAttr
        parse_bracket = self.parse_optional_char("[")
        if parse_bracket:
            array = self.parse_list(self.parse_optional_attribute)
            self.parse_char("]")
            return ArrayAttr.from_list(array)

        # Shorthand for FlatSymbolRefAttr
        parse_at = self.parse_optional_char("@")
        if parse_at:
            symbol_name = self.parse_alpha_num(skip_white_space=False)
            return FlatSymbolRefAttr.from_str(symbol_name)

        def parse_integer_type():
            self.parse_char("!", skip_white_space=skip_white_space)
            return self.parse_mlir_integer_type(
                skip_white_space=skip_white_space)

        if int_type := self.try_parse(parse_integer_type):
            return int_type

        return None

    def parse_optional_attribute(self,
                                 skip_white_space: bool = True
                                 ) -> Attribute | None:
        # If we are parsing an MLIR file, we first try to parse builtin
        # attributes, which have a different format.
        if self.source == self.Source.MLIR:
            if attr := self.parse_optional_mlir_attribute(
                    skip_white_space=skip_white_space):
                return attr

        # If we are parsing an xDSL file, we first try to parse builtin
        # attributes, which have a different format.
        if self.source == self.Source.XDSL:
            if attr := self.parse_optional_xdsl_builtin_attribute(
                    skip_white_space=skip_white_space):
                return attr

        # Then, we parse attributes/types with the generic format.

        if self.parse_optional_char("!") is None:
            if self.source == self.Source.MLIR:
                if self.parse_optional_char("#") is None:
                    return None
            else:
                return None

        parse_with_default_format = False
        # Attribute with default format
        if self.parse_optional_char('"'):
            attr_def_name = self.parse_alpha_num(skip_white_space=False)
            self.parse_char('"')
            parse_with_default_format = True
        else:
            attr_def_name = self.parse_alpha_num(skip_white_space=True)

        if (self.source == self.Source.MLIR) and parse_with_default_format:
            raise ParserError(self._pos, "cannot parse generic MLIR attribute")

        attr_def = self.ctx.get_attr(attr_def_name)

        # Attribute with default format
        if parse_with_default_format:
            if not issubclass(attr_def, ParametrizedAttribute):
                raise ParserError(
                    self._pos,
                    f"{attr_def_name} is not a parameterized attribute, and "
                    "thus cannot be parsed with a generic format.")
            params = self.parse_paramattr_parameters()
            return attr_def(params)  # type: ignore

        if issubclass(attr_def, Data):
            self.parse_char("<")
            attr: Any = attr_def.parse_parameter(self)
            self.parse_char(">")
            return attr_def(attr)  # type: ignore

        assert issubclass(attr_def, ParametrizedAttribute)
        param_list = attr_def.parse_parameters(self)
        return attr_def(param_list)  # type: ignore

    def parse_optional_dim(self, skip_white_space: bool = True) -> int | None:
        """
        Parse an optional dimension.
        The dimension is either a non-negative integer, or -1 for dynamic dimensions.
        """
        if self.parse_optional_char("?", skip_white_space=skip_white_space):
            return -1
        if (dim := self.parse_optional_int_literal()) is not None:
            return dim
        return None

    def parse_dim(self, skip_white_space: bool = True) -> int:
        """
        Parse a dimension.
        The dimension is either a non-negative integer,
        or -1 for dynamic dimensions, represented by `?`.
        """
        dim = self.parse_optional_dim(skip_white_space=skip_white_space)
        if dim is not None:
            return dim
        raise ParserError(self._pos, "dimension expected")

    def parse_optional_shape(
            self,
            skip_white_space: bool = True
    ) -> tuple[list[int], Attribute] | None:
        """
        Parse a shape, with the format `dim0 x dim1 x ... x dimN x type`.
        """
        dims = list[int]()

        if skip_white_space:
            self.skip_white_space()

        def parse_optional_dim_and_x():
            if (dim := self.parse_optional_dim(
                    skip_white_space=False)) is not None:
                self.parse_char("x", skip_white_space=False)
                return dim
            return None

        dims = self.parse_list(parse_optional_dim_and_x, delimiter="")
        typ = self.parse_attribute()

        return dims, typ

    def parse_shape(
            self,
            skip_white_space: bool = True) -> tuple[list[int], Attribute]:
        """
        Parse a shape, with the format `dim0 x dim1 x ... x dimN x type`.
        """
        shape = self.parse_optional_shape(skip_white_space=skip_white_space)
        if shape is not None:
            return shape
        raise ParserError(self._pos, "shape expected")

    def parse_optional_mlir_tensor(
        self,
        skip_white_space: bool = True
    ) -> AnyTensorType | AnyUnrankedTensorType | None:
        if self.parse_optional_string("tensor",
                                      skip_white_space=skip_white_space):
            self.parse_char("<")
            # Unranked tensor case
            if self.parse_optional_char("*"):
                self.parse_char("x")
                typ = self.parse_attribute()
                self.parse_char(">")
                return UnrankedTensorType.from_type(typ)
            dims, typ = self.parse_shape()
            self.parse_char(">")
            return TensorType.from_type_and_list(typ, dims)
        return None

    def parse_optional_mlir_vector(self,
                                   skip_white_space: bool = True
                                   ) -> AnyVectorType | None:
        if self.parse_optional_string("vector",
                                      skip_white_space=skip_white_space):
            self.parse_optional_char("<")
            dims, typ = self.parse_shape()
            self.parse_char(">")
            return VectorType.from_type_and_list(typ, dims)
        return None

    def parse_optional_mlir_index_type(self,
                                       skip_white_space: bool = True
                                       ) -> IndexType | None:
        if self.parse_optional_string("index",
                                      skip_white_space=skip_white_space):
            return IndexType()
        return None

    def parse_mlir_index_type(self,
                              skip_white_space: bool = True) -> IndexType:
        typ = self.parse_optional_mlir_index_type(
            skip_white_space=skip_white_space)
        if typ is not None:
            return typ
        raise ParserError(self._pos, "index type expected")

    def parse_mlir_integer_type(self,
                                skip_white_space: bool = True) -> IntegerType:
        # Parse the optional signedness semantics
        if self.parse_optional_string("si", skip_white_space=skip_white_space):
            signedness = Signedness.SIGNED
        elif self.parse_optional_string("ui",
                                        skip_white_space=skip_white_space):
            signedness = Signedness.UNSIGNED
        elif self.parse_optional_string("i",
                                        skip_white_space=skip_white_space):
            signedness = Signedness.SIGNLESS
        else:
            raise ParserError(self._pos, "integer type expected")

        val = self.parse_int_literal(skip_white_space=False)
        return IntegerType.from_width(val, signedness)

    def parse_optional_mlir_integer_type(self,
                                         skip_white_space: bool = True
                                         ) -> IntegerType | None:
        return self.try_parse(self.parse_mlir_integer_type,
                              skip_white_space=skip_white_space)

    def parse_optional_mlir_float_type(self,
                                       skip_white_space: bool = True
                                       ) -> AnyFloat | None:
        if self.parse_optional_string("f16") is not None:
            return Float16Type()
        if self.parse_optional_string("f32") is not None:
            return Float32Type()
        if self.parse_optional_string("f64") is not None:
            return Float64Type()
        return None

    def parse_mlir_float_type(self, skip_white_space: bool = True) -> AnyFloat:
        typ = self.parse_optional_mlir_float_type(
            skip_white_space=skip_white_space)
        if typ is not None:
            return typ
        raise ParserError(self._pos, "float type expected")

    def parse_optional_mlir_attribute(self,
                                      skip_white_space: bool = True
                                      ) -> Attribute | None:
        if skip_white_space:
            self.skip_white_space()

        # index type
        if (index_type := self.parse_optional_mlir_index_type()) is not None:
            return index_type

        # integer type
        if (int_type := self.parse_optional_mlir_integer_type()) is not None:
            return int_type

        # float type
        if (float_type := self.parse_optional_mlir_float_type()) is not None:
            return float_type

        # float attribute
        if (lit := self.parse_optional_float_literal()) is not None:
            if self.parse_optional_char(":"):
                if (typ := self.parse_optional_mlir_float_type()) is not None:
                    return FloatAttr.from_value(lit, typ)
                raise ParserError(self._pos, "float type expected")
            return FloatAttr.from_value(lit, Float64Type())

        # Shorthand for boolean attributes (integer attributes of width 1)
        if (bool_attr := self.parse_optional_boolean_attribute()) is not None:
            return bool_attr

        # integer attribute
        if (lit := self.parse_optional_int_literal()) is not None:
            if self.parse_optional_char(":"):
                if (typ :=
                        self.parse_optional_mlir_integer_type()) is not None:
                    return IntegerAttr.from_params(lit, typ)
                if (typ := self.parse_optional_mlir_index_type()) is not None:
                    return IntegerAttr.from_params(lit, typ)
                raise ParserError(self._pos, "integer or index type expected")
            return IntegerAttr.from_params(lit, IntegerType.from_width(64))

        # string literal
        str_literal = self.parse_optional_str_literal()
        if str_literal is not None:
            return StringAttr.from_str(str_literal)

        # Array attribute
        if self.parse_optional_char("["):
            contents = self.parse_list(self.parse_optional_attribute)
            self.parse_char("]")
            return ArrayAttr.from_list(contents)

        # tensor type
        if (tensor := self.parse_optional_mlir_tensor()) is not None:
            return tensor

        # vector type
        if (vector := self.parse_optional_mlir_vector()) is not None:
            return vector

        # dense attribute
        if self.parse_optional_string("dense"):
            self.parse_char("<")

            def parse_num() -> int | float | None:
                if (f := self.parse_optional_float_literal()) is not None:
                    return f
                if (i := self.parse_optional_int_literal()) is not None:
                    return i
                return None

            value = self.parse_optional_nested_list(parse_num)
            self.parse_char(">")
            self.parse_char(":")

            # Parse the dense attribute type. It is either a tensor or a vector.
            loc = self._pos
            type_attr: AnyVectorType | AnyTensorType
            if (vec := self.parse_optional_mlir_vector()) is not None:
                type_attr = vec
            elif (tensor := self.parse_optional_mlir_tensor()) is not None:
                type_attr = tensor
            else:
                raise ParserError(loc, "expected a tensor or a vector type")

            return DenseIntOrFPElementsAttr.from_list(type_attr, value)

        # opaque attribute
        if self.parse_optional_string("opaque") is not None:
            self.parse_char("<")
            name = self.parse_str_literal()
            self.parse_char(",")
            val: str = self.parse_str_literal()
            self.parse_char(">")
            if self.parse_optional_char(":") is not None:
                typ = self.parse_attribute()
                return OpaqueAttr.from_strings(name, val, typ)
            return OpaqueAttr.from_strings(name, val)

        # function attribute
        if self.parse_optional_char("(") is not None:
            inputs = self.parse_list(self.parse_optional_attribute)
            self.parse_char(")")
            self.parse_string("->")
            if self.parse_optional_char("("):
                outputs = self.parse_list(self.parse_optional_attribute)
                self.parse_char(")")
                return FunctionType.from_lists(inputs, outputs)
            output = self.parse_attribute()
            return FunctionType.from_lists(inputs, [output])

        return None

    def parse_attribute(self, skip_white_space: bool = True) -> Attribute:
        res = self.parse_optional_attribute(skip_white_space=skip_white_space)
        if res is None:
            raise ParserError(self._pos, "attribute expected")
        return res

    def parse_optional_named_attribute(
            self,
            skip_white_space: bool = True) -> tuple[str, Attribute] | None:
        # The attribute name is either a string literal, or an identifier.
        attr_name = self.parse_optional_str_literal(
            skip_white_space=skip_white_space)
        if attr_name is None:
            attr_name = self.parse_optional_alpha_num(
                skip_white_space=skip_white_space)

        if attr_name is None:
            return None
        if not self.peek_char("="):
            return attr_name, UnitAttr([])
        self.parse_char("=")
        attr = self.parse_attribute()
        return attr_name, attr

    def parse_op_attributes(self,
                            skip_white_space: bool = True
                            ) -> dict[str, Attribute]:
        if not self.parse_optional_char(
                "[" if self.source == self.Source.XDSL else "{",
                skip_white_space=skip_white_space):
            return dict()
        attrs_with_names = self.parse_list(self.parse_optional_named_attribute)
        self.parse_char("]" if self.source == self.Source.XDSL else "}")
        return {name: attr for (name, attr) in attrs_with_names}

    def parse_optional_successor(self,
                                 skip_white_space: bool = True
                                 ) -> Block | None:
        parsed = self.parse_optional_char("^",
                                          skip_white_space=skip_white_space)
        if parsed is None:
            return None
        bb_name = self.parse_alpha_num(skip_white_space=False)
        if bb_name in self._blocks:
            block = self._blocks[bb_name]
            pass
        else:
            block = Block()
            self._blocks[bb_name] = block
        return block

    def parse_successors(self, skip_white_space: bool = True) -> list[Block]:
        parsed = self.parse_optional_char(
            "(" if self.source == self.Source.XDSL else "[",
            skip_white_space=skip_white_space)
        if parsed is None:
            return []
        res = self.parse_list(self.parse_optional_successor, delimiter=',')
        self.parse_char(")" if self.source == self.Source.XDSL else "]")
        return res

    def is_valid_name(self, name: str) -> bool:
        return not name[-1].isnumeric()

    _OperationType = TypeVar('_OperationType', bound='Operation')

    def parse_op_with_default_format(
            self,
            op_type: type[_OperationType],
            result_types: list[Attribute],
            skip_white_space: bool = True) -> _OperationType:
        operands = self.parse_operands(skip_white_space=skip_white_space)
        successors = self.parse_successors()
        attributes = self.parse_op_attributes()
        regions = self.parse_list(self.parse_optional_region, delimiter="")

        return op_type.create(operands=operands,
                              result_types=result_types,
                              attributes=attributes,
                              successors=successors,
                              regions=regions)

    def _parse_optional_op_name(self,
                                skip_white_space: bool = True
                                ) -> tuple[str, bool] | None:
        op_name = self.parse_optional_alpha_num(
            skip_white_space=skip_white_space)
        if op_name:
            return op_name, False
        op_name = self.parse_optional_str_literal()
        if op_name:
            return op_name, True
        return None

    def _parse_op_name(self,
                       skip_white_space: bool = True) -> tuple[str, bool]:
        op_name = self._parse_optional_op_name(
            skip_white_space=skip_white_space)
        if op_name is None:
            raise ParserError(self._pos, "operation name expected")
        return op_name

    def parse_optional_op(self,
                          skip_white_space: bool = True) -> Operation | None:
        if self.source == self.Source.MLIR:
            return self.parse_optional_mlir_op(
                skip_white_space=skip_white_space)

        start_pos = self._pos
        results = self.parse_optional_typed_results(
            skip_white_space=skip_white_space)
        if results is None:
            op_name_and_generic = self._parse_optional_op_name()
            if op_name_and_generic is None:
                return None
            op_name, is_generic_format = op_name_and_generic
            results = []
        else:
            op_name, is_generic_format = self._parse_op_name()

        result_types = [typ for (_, typ) in results]
        op_type = self.ctx.get_optional_op(op_name)

        # If the operation is not registered, we create an UnregisteredOp instead,
        # or fail.
        if op_type is None:
            if not self.allow_unregistered_ops:
                raise ParserError(start_pos, f"unknown operation '{op_name}'")
            if not is_generic_format:
                raise ParserError(
                    start_pos, f"unknown operation '{op_name}' can "
                    "only be parsed using the generic format")

            op = self.parse_op_with_default_format(UnregisteredOp,
                                                   result_types)
            op.attributes["op_name__"] = StringAttr.from_str(op_name)
        else:
            if not is_generic_format:
                op = op_type.parse(result_types, self)
            else:
                op = self.parse_op_with_default_format(op_type, result_types)

        # Register the SSA value names in the parser
        for (idx, res) in enumerate(results):
            if res[0] in self._ssaValues:
                raise ParserError(start_pos,
                                  f"SSA value {res[0]} is already defined")
            self._ssaValues[res[0]] = op.results[idx]
            if self.is_valid_name(res[0]):
                self._ssaValues[res[0]].name = res[0]

        return op

    def parse_op_type(
        self,
        skip_white_space: bool = True
    ) -> tuple[list[Attribute], list[Attribute]]:
        self.parse_char("(", skip_white_space=skip_white_space)
        inputs = self.parse_list(self.parse_optional_attribute)
        self.parse_char(")")
        self.parse_string("->")

        # No or multiple result types
        if self.parse_optional_char("("):
            outputs = self.parse_list(self.parse_optional_attribute)
            self.parse_char(")")
        else:
            outputs = [self.parse_attribute()]

        return inputs, outputs

    def parse_mlir_op_with_default_format(
            self,
            op_type: type[_OperationType],
            num_results: int,
            skip_white_space: bool = True) -> _OperationType:
        operands = self.parse_operands(skip_white_space=skip_white_space)

        regions = []
        if self.parse_optional_char("(") is not None:
            regions = self.parse_list(self.parse_optional_region)
            self.parse_char(")")

        attributes = self.parse_op_attributes()

        self.parse_char(":")
        operand_types, result_types = self.parse_op_type()

        if len(operand_types) != len(operands):
            raise Exception(
                "Operand types are not matching the number of operands.")
        if len(result_types) != num_results:
            raise Exception(
                "Result types are not matching the number of results.")
        for operand, operand_type in zip(operands, operand_types):
            if operand.typ != operand_type:
                raise Exception("Operation operand types are not matching "
                                "the types of its operands. Got operand with "
                                f"type {operand.typ}, but operation expect "
                                f"operand to be of type {operand_type}")

        return op_type.create(operands=operands,
                              result_types=result_types,
                              attributes=attributes,
                              regions=regions)

    def parse_optional_mlir_op(self,
                               skip_white_space: bool = True
                               ) -> Operation | None:
        start_pos = self._pos
        results = self.parse_optional_results(
            skip_white_space=skip_white_space)
        if results is None:
            results = []
            op_name = self.parse_optional_str_literal()
            if op_name is None:
                return None
        else:
            op_name = self.parse_str_literal()

        op_type = self.ctx.get_optional_op(op_name)
        if op_type is None:
            if not self.allow_unregistered_ops:
                raise ParserError(start_pos, f"unknown operation '{op_name}'")

            op_type = UnregisteredOp
            op = self.parse_mlir_op_with_default_format(op_type, len(results))
            op.attributes["op_name__"] = StringAttr.from_str(op_name)
        else:
            op = self.parse_mlir_op_with_default_format(op_type, len(results))

        # Register the SSA value names in the parser
        for (idx, res) in enumerate(results):
            if res in self._ssaValues:
                raise ParserError(start_pos,
                                  f"SSA value {res} is already defined")
            self._ssaValues[res] = op.results[idx]
            if self.is_valid_name(res):
                self._ssaValues[res].name = res

        return op

    def parse_op(self, skip_white_space: bool = True) -> Operation:
        res = self.parse_optional_op(skip_white_space=skip_white_space)
        if res is None:
            raise ParserError(self._pos, "operation expected")
        return res
