Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions tests/irdl/test_attr_declarative_assembly_format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Tests for the attribute/type declarative assembly format plumbing.

These tests drive ``AttrFormatProgram`` directly. At this stage only the empty
format is supported; concrete directives are added in later commits.
"""

from __future__ import annotations

from io import StringIO

import pytest

from xdsl.context import Context
from xdsl.ir import Attribute, ParametrizedAttribute, TypeAttribute
from xdsl.irdl import irdl_attr_definition
from xdsl.irdl.declarative_assembly_format import AttrFormatProgram
from xdsl.parser import Parser
from xdsl.printer import Printer
from xdsl.utils.exceptions import ParseError


@irdl_attr_definition
class EmptyType(ParametrizedAttribute, TypeAttribute):
name = "test_af.empty"

Comment thread
superlopuh marked this conversation as resolved.

def _program(format_str: str) -> AttrFormatProgram:
return AttrFormatProgram.from_str(format_str, EmptyType.get_irdl_definition())


def _print(format_str: str) -> str:
output = StringIO()
_program(format_str).print(Printer(stream=output), EmptyType())
return output.getvalue()


def _parse(format_str: str, body: str) -> list[Attribute]:
parser = Parser(Context(allow_unregistered=True), body)
return _program(format_str).parse(parser, EmptyType.get_irdl_definition())


def test_empty_format_produces_empty_program():
assert _program("").stmts == ()


def test_empty_format_prints_and_parses_nothing():
assert _print("") == ""
assert _parse("", "") == []


def test_parse_attribute_end_to_end():
ctx = Context()
ctx.load_attr_or_type(EmptyType)
assert Parser(ctx, "!test_af.empty").parse_type() == EmptyType()


def test_error_unexpected_token():
with pytest.raises(ParseError, match="unexpected token"):
_program("$foo")
82 changes: 81 additions & 1 deletion xdsl/irdl/declarative_assembly_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,15 @@
IRDLOperationInvT,
OpDef,
OptionalDef,
ParamAttrDef,
Successor,
VarIRConstruct,
VarOperand,
get_construct_defs,
is_const_classvar,
verify_variadic_same_size,
)
from xdsl.parser import Parser, UnresolvedOperand
from xdsl.parser import AttrParser, Parser, UnresolvedOperand
from xdsl.printer import Printer
from xdsl.utils.exceptions import PyRDLError, VerifyException
from xdsl.utils.hints import isa
Expand Down Expand Up @@ -1486,3 +1487,82 @@ def set_empty(self, state: ParsingState) -> None:
element.set_empty(state)
for element in self.else_elements:
element.set_empty(state)


# ===========================================================================
# Attribute/Type declarative assembly format
# ===========================================================================


@dataclass
class AttrParsingState:
"""
State during parsing of a ParametrizedAttribute using declarative format.
"""

parameters: list[Attribute | None]
attr_def: ParamAttrDef

def __init__(self, attr_def: ParamAttrDef):
self.attr_def = attr_def
self.parameters = [None] * len(attr_def.parameters)


@dataclass(frozen=True)
class AttrFormatDirective(ABC):
"""
Base class for attribute/type format directives.

Separate from FormatDirective because operation formats parse full ops
(operands, results, regions, etc.) using Parser and ParsingState, while
attribute/type formats only fill parameter slots of a ParametrizedAttribute
using AttrParser and AttrParsingState.
"""

@abstractmethod
def parse(self, parser: AttrParser, state: AttrParsingState) -> bool:
"""
Parse the directive, returning True if input was consumed.
"""

@abstractmethod
def print(
self, printer: Printer, state: PrintingState, attr: ParametrizedAttribute, /
) -> None:
"""
Print the directive.
"""


@dataclass(frozen=True)
class AttrFormatProgram:
"""
The toplevel data structure of a declarative assembly format program
for attributes/types.
"""

stmts: tuple[AttrFormatDirective, ...]
"""
The statements composing the program. They are executed in order.
"""

@staticmethod
def from_str(format_str: str, attr_def: ParamAttrDef) -> AttrFormatProgram:
from xdsl.irdl.declarative_assembly_format_parser import AttrFormatParser

return AttrFormatParser(format_str, attr_def).parse_format()

def parse(self, parser: AttrParser, attr_def: ParamAttrDef) -> list[Attribute]:
"""
Return the parsed parameter values.

Only the empty format is supported at this stage, so there are no
directives to run yet; IRDL constraint verification runs when the
attribute is constructed, not during this parse step.
"""
assert not self.stmts
state = AttrParsingState(attr_def)
return cast(list[Attribute], state.parameters)

def print(self, printer: Printer, attr: ParametrizedAttribute) -> None:
assert not self.stmts
23 changes: 23 additions & 0 deletions xdsl/irdl/declarative_assembly_format_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
OptSingleBlockRegionDef,
OptSuccessorDef,
ParamAttrConstraint,
ParamAttrDef,
ParsePropInAttrDict,
SameVariadicOperandSize,
SameVariadicResultSize,
Expand All @@ -45,6 +46,8 @@
)
from xdsl.irdl.declarative_assembly_format import (
AttrDictDirective,
AttrFormatDirective,
AttrFormatProgram,
AttributeVariable,
DenseArrayAttributeVariable,
Directive,
Expand Down Expand Up @@ -900,3 +903,23 @@ def create_results_directive(self, inside_ref: bool) -> ResultsDirective:
if not inside_ref:
self.seen_result_types = [True] * len(self.seen_result_types)
return ResultsDirective()


@dataclass(init=False)
class AttrFormatParser(BaseParser):
"""Parser for attribute/type declarative assembly format strings."""

attr_def: ParamAttrDef

def __init__(self, input_str: str, attr_def: ParamAttrDef):
super().__init__(ParserState(FormatLexer(Input(input_str, "<attr-format>"))))
self.attr_def = attr_def

def parse_format(self) -> AttrFormatProgram:
elements: list[AttrFormatDirective] = []
while self._current_token.kind != MLIRTokenKind.EOF:
elements.append(self.parse_format_directive())
return AttrFormatProgram(tuple(elements))

def parse_format_directive(self) -> AttrFormatDirective:
self.raise_error(f"unexpected token '{self._current_token.text}'")
Loading