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
30 changes: 21 additions & 9 deletions tests/irdl/test_attr_declarative_assembly_format.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -43,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():
Expand All @@ -54,6 +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")
@pytest.mark.parametrize(
"format_str, error",
[
("$foo", "unexpected token"),
("` `", "unexpected whitespace in directive"),
("`+`", "expected a whitespace directive"),
],
)
def test_error(format_str: str, error: str):
with pytest.raises(ParseError, match=error):
_program(format_str)
56 changes: 47 additions & 9 deletions xdsl/irdl/declarative_assembly_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand All @@ -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)
Expand Down Expand Up @@ -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:
"""
Expand All @@ -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)
27 changes: 27 additions & 0 deletions xdsl/irdl/declarative_assembly_format_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
AttrFormatDirective,
AttrFormatProgram,
AttributeVariable,
AttrWhitespaceDirective,
DenseArrayAttributeVariable,
Directive,
FormatDirective,
Expand Down Expand Up @@ -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")
Loading