From 31aa6039b46026a6b78a4ed5623beb8c4eea6934 Mon Sep 17 00:00:00 2001 From: Nicolai Stawinoga Date: Mon, 13 Jul 2026 15:47:06 +0200 Subject: [PATCH 1/2] core: Attribute declarative asm - whitespace --- .../test_attr_declarative_assembly_format.py | 37 ++++++++++-- xdsl/irdl/declarative_assembly_format.py | 56 ++++++++++++++++--- .../declarative_assembly_format_parser.py | 27 +++++++++ 3 files changed, 107 insertions(+), 13 deletions(-) diff --git a/tests/irdl/test_attr_declarative_assembly_format.py b/tests/irdl/test_attr_declarative_assembly_format.py index df548df5bd..371a61ddb4 100644 --- a/tests/irdl/test_attr_declarative_assembly_format.py +++ b/tests/irdl/test_attr_declarative_assembly_format.py @@ -1,7 +1,7 @@ -"""Tests for the attribute/type declarative assembly format plumbing. +"""Tests for the attribute/type declarative assembly format. -These tests drive ``AttrFormatProgram`` directly. At this stage only the empty -format is supported; concrete directives are added in later commits. +These tests drive ``AttrFormatProgram`` directly, covering the empty format and +the structural directives (whitespace, punctuation, keyword). """ from __future__ import annotations @@ -13,7 +13,10 @@ 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.irdl.declarative_assembly_format import ( + AttrFormatProgram, + AttrWhitespaceDirective, +) from xdsl.parser import Parser from xdsl.printer import Printer from xdsl.utils.exceptions import ParseError @@ -57,3 +60,29 @@ def test_parse_attribute_end_to_end(): def test_error_unexpected_token(): with pytest.raises(ParseError, match="unexpected token"): _program("$foo") + + +# Whitespace directive + + +@pytest.mark.parametrize( + "format_str, expected", + [("` `", " "), ("``", ""), ("`\\n`", "\n")], +) +def test_whitespace_print(format_str: str, expected: str): + assert _print(format_str) == expected + + +def test_whitespace_parses_to_directive_and_consumes_nothing(): + assert _program("` `").stmts == (AttrWhitespaceDirective(" "),) + assert _parse("` `", "") == [] + + +def test_error_invalid_whitespace(): + with pytest.raises(ParseError, match="unexpected whitespace in directive"): + _program("` `") + + +def test_error_not_a_whitespace_directive(): + with pytest.raises(ParseError, match="expected a whitespace directive"): + _program("`+`") diff --git a/xdsl/irdl/declarative_assembly_format.py b/xdsl/irdl/declarative_assembly_format.py index e45cc00917..ee70c481ec 100644 --- a/xdsl/irdl/declarative_assembly_format.py +++ b/xdsl/irdl/declarative_assembly_format.py @@ -1359,6 +1359,18 @@ def is_optional_like(self) -> bool: return False +# =========================================================================== +# Shared print helpers for the structural directives (whitespace, punctuation, +# keyword), used by both the op-side and attr-side directive classes. +# =========================================================================== + + +def _print_whitespace(printer: Printer, state: PrintingState, whitespace: str) -> None: + printer.print_string(whitespace) + state.last_was_punctuation = whitespace == "" + state.should_emit_space = False + + @dataclass(frozen=True) class WhitespaceDirective(FormatDirective): """ @@ -1376,9 +1388,7 @@ def parse(self, parser: Parser, state: ParsingState) -> bool: return False def print(self, printer: Printer, state: PrintingState, op: IRDLOperation) -> None: - printer.print_string(self.whitespace) - state.last_was_punctuation = self.whitespace == "" - state.should_emit_space = False + _print_whitespace(printer, state, self.whitespace) @dataclass(frozen=True) @@ -1534,6 +1544,32 @@ def print( """ +# =========================================================================== +# Attr-side structural directives +# =========================================================================== + + +@dataclass(frozen=True) +class AttrWhitespaceDirective(AttrFormatDirective): + """ + A whitespace directive for attribute/type assembly format. + + Mirrors the op-side WhitespaceDirective: only applied during printing, + with no effect during parsing. + """ + + whitespace: Literal[" ", "\n", ""] + """The whitespace that should be printed.""" + + def parse(self, parser: AttrParser, state: AttrParsingState) -> bool: + return False + + def print( + self, printer: Printer, state: PrintingState, attr: ParametrizedAttribute, / + ) -> None: + _print_whitespace(printer, state, self.whitespace) + + @dataclass(frozen=True) class AttrFormatProgram: """ @@ -1554,15 +1590,17 @@ def from_str(format_str: str, attr_def: ParamAttrDef) -> AttrFormatProgram: def parse(self, parser: AttrParser, attr_def: ParamAttrDef) -> list[Attribute]: """ - Return the parsed parameter values. + Run each directive in order, returning 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. + IRDL constraint verification runs when the attribute is constructed, + not during this parse step. """ - assert not self.stmts state = AttrParsingState(attr_def) + for stmt in self.stmts: + stmt.parse(parser, state) return cast(list[Attribute], state.parameters) def print(self, printer: Printer, attr: ParametrizedAttribute) -> None: - assert not self.stmts + state = PrintingState(last_was_punctuation=True, should_emit_space=False) + for stmt in self.stmts: + stmt.print(printer, state, attr) diff --git a/xdsl/irdl/declarative_assembly_format_parser.py b/xdsl/irdl/declarative_assembly_format_parser.py index 71576e82ab..ded3eccbb4 100644 --- a/xdsl/irdl/declarative_assembly_format_parser.py +++ b/xdsl/irdl/declarative_assembly_format_parser.py @@ -49,6 +49,7 @@ AttrFormatDirective, AttrFormatProgram, AttributeVariable, + AttrWhitespaceDirective, DenseArrayAttributeVariable, Directive, FormatDirective, @@ -922,4 +923,30 @@ def parse_format(self) -> AttrFormatProgram: return AttrFormatProgram(tuple(elements)) def parse_format_directive(self) -> AttrFormatDirective: + if self._current_token.text == "`": + return self.parse_keyword_or_punctuation() self.raise_error(f"unexpected token '{self._current_token.text}'") + + def parse_keyword_or_punctuation(self) -> AttrFormatDirective: + start_token = self._current_token + self.parse_characters("`") + + # New line case + if self.parse_optional_keyword("\\"): + self.parse_keyword("n") + self.parse_characters("`") + return AttrWhitespaceDirective("\n") + + # Space or empty backtick case + end_token = self._current_token + if self.parse_optional_characters("`"): + whitespace = self.lexer.input.content[ + start_token.span.end : end_token.span.start + ] + if whitespace != " " and whitespace != "": + self.raise_error( + "unexpected whitespace in directive, only ` ` or `` whitespace is allowed" + ) + return AttrWhitespaceDirective(whitespace) + + self.raise_error("expected a whitespace directive") From 9ab0978d26164611ca041e5b8ed2d6e82ce3a04d Mon Sep 17 00:00:00 2001 From: Nicolai Stawinoga Date: Mon, 13 Jul 2026 16:52:27 +0200 Subject: [PATCH 2/2] Restructure tests --- .../test_attr_declarative_assembly_format.py | 51 +++++++------------ 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/tests/irdl/test_attr_declarative_assembly_format.py b/tests/irdl/test_attr_declarative_assembly_format.py index 371a61ddb4..c313f467a6 100644 --- a/tests/irdl/test_attr_declarative_assembly_format.py +++ b/tests/irdl/test_attr_declarative_assembly_format.py @@ -13,10 +13,7 @@ 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, - AttrWhitespaceDirective, -) +from xdsl.irdl.declarative_assembly_format import AttrFormatProgram from xdsl.parser import Parser from xdsl.printer import Printer from xdsl.utils.exceptions import ParseError @@ -46,9 +43,13 @@ def test_empty_format_produces_empty_program(): assert _program("").stmts == () -def test_empty_format_prints_and_parses_nothing(): - assert _print("") == "" - assert _parse("", "") == [] +@pytest.mark.parametrize( + "format_str, printed", + [("", ""), ("` `", " "), ("``", ""), ("`\\n`", "\n")], +) +def test_prints_and_parses_nothing(format_str: str, printed: str): + assert _print(format_str) == printed + assert _parse(format_str, "") == [] def test_parse_attribute_end_to_end(): @@ -57,32 +58,14 @@ def test_parse_attribute_end_to_end(): assert Parser(ctx, "!test_af.empty").parse_type() == EmptyType() -def test_error_unexpected_token(): - with pytest.raises(ParseError, match="unexpected token"): - _program("$foo") - - -# Whitespace directive - - @pytest.mark.parametrize( - "format_str, expected", - [("` `", " "), ("``", ""), ("`\\n`", "\n")], + "format_str, error", + [ + ("$foo", "unexpected token"), + ("` `", "unexpected whitespace in directive"), + ("`+`", "expected a whitespace directive"), + ], ) -def test_whitespace_print(format_str: str, expected: str): - assert _print(format_str) == expected - - -def test_whitespace_parses_to_directive_and_consumes_nothing(): - assert _program("` `").stmts == (AttrWhitespaceDirective(" "),) - assert _parse("` `", "") == [] - - -def test_error_invalid_whitespace(): - with pytest.raises(ParseError, match="unexpected whitespace in directive"): - _program("` `") - - -def test_error_not_a_whitespace_directive(): - with pytest.raises(ParseError, match="expected a whitespace directive"): - _program("`+`") +def test_error(format_str: str, error: str): + with pytest.raises(ParseError, match=error): + _program(format_str)