Skip to content

Commit

Permalink
feat: new item predicates syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
vberlier committed Apr 10, 2024
1 parent 8eec916 commit 1cb0b8e
Show file tree
Hide file tree
Showing 26 changed files with 808 additions and 130 deletions.
52 changes: 46 additions & 6 deletions mecha/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@
"AstBlockState",
"AstBlock",
"AstItemComponent",
"AstItem",
"AstItemStack",
"AstItemPredicateTestComponent",
"AstItemPredicateTestPredicate",
"AstItemPredicateAlternatives",
"AstItemPredicate",
"AstItemSlot",
"AstItemSlots",
"AstRange",
Expand Down Expand Up @@ -914,12 +918,48 @@ class AstItemComponent(AstNode):


@dataclass(frozen=True, slots=True)
class AstItem(AstNode):
"""Ast item node."""
class AstItemStack(AstNode):
"""Ast item stack node."""

identifier: AstResourceLocation = required_field()
components: AstChildren[AstItemComponent] = AstChildren()
data_tags: Optional[AstNbtCompound] = None
arguments: AstChildren[AstItemComponent] = AstChildren()
data_tags: Optional[AstNbtCompound] = None # legacy compat


@dataclass(frozen=True, slots=True)
class AstItemPredicateTestComponent(AstNode):
"""Ast item predicate test component node."""

inverted: bool = False
key: AstResourceLocation = required_field()
value: Optional[AstNbt] = None


@dataclass(frozen=True, slots=True)
class AstItemPredicateTestPredicate(AstNode):
"""Ast item predicate test predicate node."""

inverted: bool = False
key: AstResourceLocation = required_field()
value: AstNbt = required_field()


@dataclass(frozen=True, slots=True)
class AstItemPredicateAlternatives(AstNode):
"""Ast item predicate alternatives node."""

alternatives: AstChildren[
Union[AstItemPredicateTestComponent, AstItemPredicateTestPredicate]
] = required_field()


@dataclass(frozen=True, slots=True)
class AstItemPredicate(AstNode):
"""Ast item predicate node."""

identifier: Union[AstResourceLocation, AstWildcard] = required_field()
arguments: AstChildren[AstItemPredicateAlternatives] = AstChildren()
data_tags: Optional[AstNbtCompound] = None # legacy compat


@dataclass(frozen=True, slots=True)
Expand Down Expand Up @@ -1238,7 +1278,7 @@ class AstFallingDustParticleParameters(AstParticleParameters):
class AstItemParticleParameters(AstParticleParameters):
"""Ast item particle parameters node."""

item: AstItem = required_field()
item: AstItemStack = required_field()


@dataclass(frozen=True, slots=True)
Expand Down
165 changes: 120 additions & 45 deletions mecha/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@
"ResourceLocationParser",
"NoTagConstraint",
"BlockParser",
"BracketedKeyValuePairsParser",
"BracketedListParser",
"KeyValuePairParser",
"ItemParser",
"ItemPredicateAlternativesParser",
"NoDataTagsConstraint",
"BasicLiteralParser",
"RangeParser",
Expand Down Expand Up @@ -108,11 +110,15 @@
AstGamemode,
AstGreedy,
AstHeightmap,
AstItem,
AstItemComponent,
AstItemParticleParameters,
AstItemPredicate,
AstItemPredicateAlternatives,
AstItemPredicateTestComponent,
AstItemPredicateTestPredicate,
AstItemSlot,
AstItemSlots,
AstItemStack,
AstJson,
AstJsonArray,
AstJsonObject,
Expand Down Expand Up @@ -248,10 +254,12 @@ def get_default_parsers() -> Dict[str, Parser]:
resource_location_parser=delegate("resource_location_or_tag"),
block_states_parser=AdjacentConstraint(
parser=MultilineParser(
BracketedKeyValuePairsParser(
node_type=AstBlockState,
key_parser=StringParser(type="phrase"),
value_parser=delegate("phrase"),
BracketedListParser(
KeyValuePairParser(
node_type=AstBlockState,
key_parser=StringParser(type="phrase"),
value_parser=delegate("phrase"),
)
)
),
hint=r"\[",
Expand All @@ -262,24 +270,30 @@ def get_default_parsers() -> Dict[str, Parser]:
resource_location_parser=delegate("resource_location"),
block_states_parser=AdjacentConstraint(
parser=MultilineParser(
BracketedKeyValuePairsParser(
node_type=AstBlockState,
key_parser=StringParser(type="phrase"),
value_parser=delegate("phrase"),
BracketedListParser(
KeyValuePairParser(
node_type=AstBlockState,
key_parser=StringParser(type="phrase"),
value_parser=delegate("phrase"),
)
)
),
hint=r"\[",
),
data_tags_parser=delegate("adjacent_nbt_compound"),
),
"item_predicate": ItemParser(
resource_location_parser=delegate("resource_location_or_tag"),
components_parser=AdjacentConstraint(
node_type=AstItemPredicate,
identifier_parser=AlternativeParser(
[delegate("resource_location_or_tag"), delegate("wildcard")]
),
arguments_parser=AdjacentConstraint(
parser=MultilineParser(
BracketedKeyValuePairsParser(
node_type=AstItemComponent,
key_parser=delegate("resource_location"),
value_parser=delegate("nbt"),
BracketedListParser(
ItemPredicateAlternativesParser(
key_parser=delegate("resource_location"),
value_parser=delegate("nbt"),
)
)
),
hint=r"\[",
Expand All @@ -289,13 +303,16 @@ def get_default_parsers() -> Dict[str, Parser]:
"item_slot": BasicLiteralParser(AstItemSlot),
"item_slots": BasicLiteralParser(AstItemSlots),
"item_stack": ItemParser(
resource_location_parser=delegate("resource_location"),
components_parser=AdjacentConstraint(
node_type=AstItemStack,
identifier_parser=delegate("resource_location"),
arguments_parser=AdjacentConstraint(
parser=MultilineParser(
BracketedKeyValuePairsParser(
node_type=AstItemComponent,
key_parser=delegate("resource_location"),
value_parser=delegate("nbt"),
BracketedListParser(
KeyValuePairParser(
node_type=AstItemComponent,
key_parser=delegate("resource_location"),
value_parser=delegate("nbt"),
)
)
),
hint=r"\[",
Expand Down Expand Up @@ -1382,67 +1399,125 @@ def __call__(self, stream: TokenStream) -> AstBlock:


@dataclass
class BracketedKeyValuePairsParser:
"""Parser for bracketed key-value pairs."""
class BracketedListParser:
"""Parser for bracketed list."""

node_type: Type[AstNode]
key_parser: Parser
value_parser: Parser
element_parser: Parser

def __call__(self, stream: TokenStream) -> AstChildren[AstNode]:
pairs: List[AstNode] = []
elements: List[AstNode] = []

with stream.syntax(
bracket=r"\[|\]",
equal=r"=",
comma=r",",
):
stream.expect(("bracket", "["))

for _ in stream.peek_until(("bracket", "]")):
key_node = self.key_parser(stream)
stream.expect("equal")
value_node = self.value_parser(stream)

entry_node = self.node_type(key=key_node, value=value_node) # type: ignore
entry_node = set_location(entry_node, key_node, value_node)
pairs.append(entry_node)
elements.append(self.element_parser(stream))

if not stream.get("comma"):
stream.expect(("bracket", "]"))
break

return AstChildren(pairs)
return AstChildren(elements)


@dataclass
class KeyValuePairParser:
"""Parser for bracketed key-value pairs."""

node_type: Type[AstNode]
key_parser: Parser
value_parser: Parser

def __call__(self, stream: TokenStream) -> AstNode:
with stream.syntax(equal=r"="):
key_node = self.key_parser(stream)
stream.expect("equal")
value_node = self.value_parser(stream)

entry_node = self.node_type(key=key_node, value=value_node) # type: ignore
return set_location(entry_node, key_node, value_node)


@dataclass
class ItemParser:
"""Parser for minecraft items."""

resource_location_parser: Parser
components_parser: Parser
node_type: Type[Any]
identifier_parser: Parser
arguments_parser: Parser
data_tags_parser: Parser

def __call__(self, stream: TokenStream) -> AstItem:
identifier = self.resource_location_parser(stream)
def __call__(self, stream: TokenStream) -> AstNode:
identifier = self.identifier_parser(stream)
location = identifier.location
end_location = identifier.end_location

if components := self.components_parser(stream):
if arguments := self.arguments_parser(stream):
end_location = stream.current.end_location
else:
components = AstChildren[AstItemComponent]()
arguments = AstChildren[AstNode]()

data_tags = self.data_tags_parser(stream)

node = AstItem(
node = self.node_type(
identifier=identifier,
components=components,
arguments=arguments,
data_tags=data_tags,
)
return set_location(node, location, data_tags if data_tags else end_location)


@dataclass
class ItemPredicateAlternativesParser:
"""Parser for item predicate alternatives."""

key_parser: Parser
value_parser: Parser

def __call__(self, stream: TokenStream) -> AstItemPredicateAlternatives:
alternatives: List[
Union[AstItemPredicateTestComponent, AstItemPredicateTestPredicate]
] = []

with stream.syntax(
pipe=r"\|",
equal=r"=",
tilde=r"~",
exclamation=r"!",
):
while True:
exclamation = stream.get("exclamation")
key_node = self.key_parser(stream)

if stream.get("tilde"):
test_node = AstItemPredicateTestPredicate(
inverted=exclamation is not None,
key=key_node,
value=self.value_parser(stream),
)
else:
test_node = AstItemPredicateTestComponent(
inverted=exclamation is not None,
key=key_node,
value=(
self.value_parser(stream) if stream.get("equal") else None
),
)

alternatives.append(
set_location(test_node, exclamation or key_node, test_node.value)
)

if not stream.get("pipe"):
break

node = AstItemPredicateAlternatives(alternatives=AstChildren(alternatives))
return set_location(node, alternatives[0], alternatives[-1])


@dataclass
class NoDataTagsConstraint:
"""Constraint that disallows data tags."""
Expand Down
Loading

0 comments on commit 1cb0b8e

Please sign in to comment.