diff --git a/tests/irdl/test_attr_declarative_assembly_format.py b/tests/irdl/test_attr_declarative_assembly_format.py new file mode 100644 index 0000000000..df548df5bd --- /dev/null +++ b/tests/irdl/test_attr_declarative_assembly_format.py @@ -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" + + +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") diff --git a/xdsl/irdl/declarative_assembly_format.py b/xdsl/irdl/declarative_assembly_format.py index 72bd0781b3..e45cc00917 100644 --- a/xdsl/irdl/declarative_assembly_format.py +++ b/xdsl/irdl/declarative_assembly_format.py @@ -37,6 +37,7 @@ IRDLOperationInvT, OpDef, OptionalDef, + ParamAttrDef, Successor, VarIRConstruct, VarOperand, @@ -44,7 +45,7 @@ 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 @@ -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 diff --git a/xdsl/irdl/declarative_assembly_format_parser.py b/xdsl/irdl/declarative_assembly_format_parser.py index b67b1a26b1..71576e82ab 100644 --- a/xdsl/irdl/declarative_assembly_format_parser.py +++ b/xdsl/irdl/declarative_assembly_format_parser.py @@ -32,6 +32,7 @@ OptSingleBlockRegionDef, OptSuccessorDef, ParamAttrConstraint, + ParamAttrDef, ParsePropInAttrDict, SameVariadicOperandSize, SameVariadicResultSize, @@ -45,6 +46,8 @@ ) from xdsl.irdl.declarative_assembly_format import ( AttrDictDirective, + AttrFormatDirective, + AttrFormatProgram, AttributeVariable, DenseArrayAttributeVariable, Directive, @@ -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, "")))) + 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}'")