bolt-native-macros 0.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bolt_native_macros/__init__.py +3 -0
- bolt_native_macros/ast.py +97 -0
- bolt_native_macros/codegen.py +52 -0
- bolt_native_macros/parse.py +248 -0
- bolt_native_macros/patches.py +27 -0
- bolt_native_macros/plugin.py +87 -0
- bolt_native_macros/serialize.py +172 -0
- bolt_native_macros/typing.py +22 -0
- bolt_native_macros-0.2.3.dist-info/METADATA +12 -0
- bolt_native_macros-0.2.3.dist-info/RECORD +11 -0
- bolt_native_macros-0.2.3.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any, Literal
|
|
3
|
+
|
|
4
|
+
from beet.core.utils import required_field
|
|
5
|
+
from mecha import AstGreedy, AstMessage, AstNbtValue, AstNode, AstString, AstWord
|
|
6
|
+
|
|
7
|
+
from .typing import MacroRepresentation, MacroTag
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"AstMacroArgument",
|
|
11
|
+
"AstMacroNbtArgument",
|
|
12
|
+
"AstMacroCoordinateArgument",
|
|
13
|
+
"AstMacroNbtPathKeyArgument",
|
|
14
|
+
"AstMacroNbtPathArgument",
|
|
15
|
+
"AstMacroRange",
|
|
16
|
+
"AstMacroStringWrapper",
|
|
17
|
+
"AstNbtValueWithMacro",
|
|
18
|
+
"AstStringWithMacro",
|
|
19
|
+
"AstGreedyWithMacro",
|
|
20
|
+
"AstWordWithMacro",
|
|
21
|
+
"AstMessageWithMacro",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class AstMacroArgument(AstNode, MacroRepresentation):
|
|
27
|
+
name: str = required_field()
|
|
28
|
+
parser: str | None = required_field()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True, slots=True)
|
|
32
|
+
class AstMacroNbtArgument(AstMacroArgument):
|
|
33
|
+
def evaluate(self):
|
|
34
|
+
return MacroTag(self.name, self.parser)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class AstMacroCoordinateArgument(AstMacroArgument):
|
|
39
|
+
type: Literal["absolute", "local", "relative"] = required_field()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True, slots=True)
|
|
43
|
+
class AstMacroNbtPathKeyArgument(AstMacroArgument): ...
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True, slots=True)
|
|
47
|
+
class AstMacroNbtPathArgument(AstMacroArgument): ...
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True, slots=True)
|
|
51
|
+
class AstMacroExpression(AstMacroArgument): ...
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True, slots=True)
|
|
55
|
+
class AstMacroRange(AstNode):
|
|
56
|
+
min: int | float | AstMacroArgument | None = field(default=None)
|
|
57
|
+
max: int | float | AstMacroArgument | None = field(default=None)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True, slots=True)
|
|
61
|
+
class AstMacroStringWrapper[N](AstNode):
|
|
62
|
+
child: N = required_field()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(frozen=True, slots=True)
|
|
66
|
+
class AstNbtValueWithMacro(AstNbtValue, MacroRepresentation):
|
|
67
|
+
@classmethod
|
|
68
|
+
def from_value(cls, value: Any) -> "AstNbtValueWithMacro":
|
|
69
|
+
return cls(value=value)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(frozen=True, slots=True)
|
|
73
|
+
class AstStringWithMacro(AstString, MacroRepresentation):
|
|
74
|
+
@classmethod
|
|
75
|
+
def from_value(cls, value: Any) -> "AstStringWithMacro":
|
|
76
|
+
return AstStringWithMacro(value=str(value))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass(frozen=True, slots=True)
|
|
80
|
+
class AstGreedyWithMacro(AstGreedy, MacroRepresentation):
|
|
81
|
+
@classmethod
|
|
82
|
+
def from_value(cls, value: Any) -> "AstGreedyWithMacro":
|
|
83
|
+
return cls(value=AstGreedy.from_value(value).value)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(frozen=True, slots=True)
|
|
87
|
+
class AstWordWithMacro(AstWord, MacroRepresentation):
|
|
88
|
+
@classmethod
|
|
89
|
+
def from_value(cls, value: Any) -> "AstWordWithMacro":
|
|
90
|
+
return cls(value=AstWord.from_value(value).value)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass(frozen=True, slots=True)
|
|
94
|
+
class AstMessageWithMacro(AstMessage, MacroRepresentation):
|
|
95
|
+
@classmethod
|
|
96
|
+
def from_value(cls, value: Any) -> "AstMessageWithMacro":
|
|
97
|
+
return cls(fragments=AstMessage.from_value(value).fragments)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Generator, List, Optional
|
|
3
|
+
|
|
4
|
+
from bolt import Accumulator, visit_generic, visit_single
|
|
5
|
+
from mecha import AstNode, Visitor, rule
|
|
6
|
+
|
|
7
|
+
from .ast import AstMacroArgument, AstMacroExpression, AstMacroStringWrapper
|
|
8
|
+
from .typing import MacroTag, StringWithMacro
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def ast_to_macro(macro: AstMacroArgument):
|
|
12
|
+
return MacroTag(macro.name, macro.parser)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def make_macro_string():
|
|
16
|
+
"""
|
|
17
|
+
Returns the type `StringWithMacro`, this is to add the type to the scope w/o making it globally accessible.
|
|
18
|
+
|
|
19
|
+
Kind of hacky but works well
|
|
20
|
+
"""
|
|
21
|
+
return StringWithMacro
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class MacroCodegen(Visitor):
|
|
26
|
+
@rule(AstMacroExpression)
|
|
27
|
+
def macro(
|
|
28
|
+
self, node: AstMacroExpression, acc: Accumulator
|
|
29
|
+
) -> Generator[AstNode, Optional[List[str]], Optional[List[str]]]:
|
|
30
|
+
# This allows for macros to be used as literals
|
|
31
|
+
result = yield from visit_generic(node, acc)
|
|
32
|
+
|
|
33
|
+
if result is None:
|
|
34
|
+
result = acc.make_ref(node)
|
|
35
|
+
|
|
36
|
+
result = acc.helper(ast_to_macro.__name__, result)
|
|
37
|
+
|
|
38
|
+
return [result]
|
|
39
|
+
|
|
40
|
+
@rule(AstMacroStringWrapper)
|
|
41
|
+
def wrapper(
|
|
42
|
+
self, node: AstMacroStringWrapper, acc: Accumulator
|
|
43
|
+
) -> Generator[AstNode, Optional[List[str]], Optional[List[str]]]:
|
|
44
|
+
# Codegen the underlying child and get its result
|
|
45
|
+
child = yield from visit_single(node.child, required=True)
|
|
46
|
+
|
|
47
|
+
# Create a variable and assign it to a new instance of StringWithMacro
|
|
48
|
+
result = acc.make_variable()
|
|
49
|
+
# make_macro_string returns the **type** StringWithMacro, you must manually create the instance afterwards
|
|
50
|
+
acc.statement(f"{result} = {acc.helper(make_macro_string.__name__)}({child})")
|
|
51
|
+
|
|
52
|
+
return [result]
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any, List, cast
|
|
3
|
+
|
|
4
|
+
from bolt import AstInterpolation
|
|
5
|
+
from mecha import (
|
|
6
|
+
NUMBER_PATTERN,
|
|
7
|
+
AlternativeParser,
|
|
8
|
+
AstChildren,
|
|
9
|
+
AstMacroLineVariable,
|
|
10
|
+
AstNbtPath,
|
|
11
|
+
AstNbtPathKey,
|
|
12
|
+
NbtPathParser,
|
|
13
|
+
Parser,
|
|
14
|
+
delegate,
|
|
15
|
+
)
|
|
16
|
+
from mecha.utils import string_to_number
|
|
17
|
+
from tokenstream import InvalidSyntax, TokenStream, set_location
|
|
18
|
+
|
|
19
|
+
from .ast import (
|
|
20
|
+
AstMacroArgument,
|
|
21
|
+
AstMacroCoordinateArgument,
|
|
22
|
+
AstMacroNbtPathArgument,
|
|
23
|
+
AstMacroNbtPathKeyArgument,
|
|
24
|
+
AstMacroRange,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class MacroParser:
|
|
30
|
+
"""
|
|
31
|
+
Used to parse typed_macro's and create the proper AstNode's
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
parser: str | tuple[str, ...]
|
|
35
|
+
node_type: type[AstMacroArgument]
|
|
36
|
+
|
|
37
|
+
def __call__(self, stream: TokenStream):
|
|
38
|
+
macro: AstMacroArgument = delegate("typed_macro", stream)
|
|
39
|
+
|
|
40
|
+
if macro.parser:
|
|
41
|
+
# Implements type checking where it makes sense, this helps to prevent unintended macro injections
|
|
42
|
+
if isinstance(self.parser, str) and macro.parser != self.parser:
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f"Invalid macro type, received {macro.parser} expected {self.parser}"
|
|
45
|
+
)
|
|
46
|
+
elif macro.parser not in self.parser:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f"Invalid macro type, received {macro.parser} expected one of {', '.join(self.parser)}"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
parser = macro.parser
|
|
52
|
+
|
|
53
|
+
# If there was no parser just use one of the intended ones for this MacroParser
|
|
54
|
+
if isinstance(self.parser, tuple) and parser is None:
|
|
55
|
+
parser = self.parser[0]
|
|
56
|
+
elif isinstance(self.parser, str):
|
|
57
|
+
parser = self.parser
|
|
58
|
+
|
|
59
|
+
# Creates the proper instance of node_type
|
|
60
|
+
if not isinstance(macro, self.node_type):
|
|
61
|
+
return self.node_type(name=macro.name, parser=parser)
|
|
62
|
+
|
|
63
|
+
return macro
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class MacroNbtPathParser(NbtPathParser):
|
|
68
|
+
"""Parser for nbt paths."""
|
|
69
|
+
|
|
70
|
+
def __call__(self, stream: TokenStream) -> AstNbtPath:
|
|
71
|
+
components: List[Any] = []
|
|
72
|
+
|
|
73
|
+
with stream.syntax(
|
|
74
|
+
dot=r"\.",
|
|
75
|
+
curly=r"\{|\}",
|
|
76
|
+
bracket=r"\[|\]",
|
|
77
|
+
quoted_string=r'"(?:\\.|[^\\\n])*?"' "|" r"'(?:\\.|[^\\\n])*?'",
|
|
78
|
+
string=r"(?:[0-9a-z_\-]+:)?[a-zA-Z0-9_+-]+",
|
|
79
|
+
):
|
|
80
|
+
components.extend(self.parse_modifiers(stream))
|
|
81
|
+
|
|
82
|
+
while not components or stream.get("dot"):
|
|
83
|
+
with stream.checkpoint() as commit:
|
|
84
|
+
macro: AstMacroArgument = delegate("typed_macro", stream)
|
|
85
|
+
|
|
86
|
+
if not macro.parser or macro.parser == "string":
|
|
87
|
+
components.append(
|
|
88
|
+
set_location(
|
|
89
|
+
AstMacroNbtPathKeyArgument(
|
|
90
|
+
name=macro.name, parser="string"
|
|
91
|
+
),
|
|
92
|
+
macro,
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
elif macro.parser == "nbt":
|
|
96
|
+
components.append(
|
|
97
|
+
set_location(
|
|
98
|
+
AstMacroNbtPathArgument(name=macro.name, parser="nbt"),
|
|
99
|
+
macro,
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
commit()
|
|
104
|
+
|
|
105
|
+
if commit.rollback:
|
|
106
|
+
quoted_string, string = stream.expect("quoted_string", "string")
|
|
107
|
+
|
|
108
|
+
if quoted_string:
|
|
109
|
+
component_node = AstNbtPathKey(
|
|
110
|
+
value=self.quote_helper.unquote_string(quoted_string),
|
|
111
|
+
)
|
|
112
|
+
components.append(set_location(component_node, quoted_string))
|
|
113
|
+
elif string:
|
|
114
|
+
component_node = AstNbtPathKey(value=string.value)
|
|
115
|
+
components.append(set_location(component_node, string))
|
|
116
|
+
|
|
117
|
+
components.extend(self.parse_modifiers(stream))
|
|
118
|
+
|
|
119
|
+
if not components:
|
|
120
|
+
raise stream.emit_error(InvalidSyntax("Empty nbt path not allowed."))
|
|
121
|
+
|
|
122
|
+
node = AstNbtPath(components=AstChildren(components))
|
|
123
|
+
return set_location(node, components[0], components[-1])
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class MacroRangeParser:
|
|
127
|
+
def get_bound(self, stream: TokenStream) -> int | float | AstMacroArgument | None:
|
|
128
|
+
if number := stream.get("number"):
|
|
129
|
+
return string_to_number(number.value)
|
|
130
|
+
|
|
131
|
+
with stream.checkpoint() as commit:
|
|
132
|
+
macro: AstMacroArgument = delegate("typed_macro", stream)
|
|
133
|
+
|
|
134
|
+
if macro.parser and macro.parser != "numeric":
|
|
135
|
+
raise ValueError(
|
|
136
|
+
f"Invalid macro type, received {macro.parser} expected numeric"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
commit()
|
|
140
|
+
|
|
141
|
+
if not commit.rollback:
|
|
142
|
+
return macro
|
|
143
|
+
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
def __call__(self, stream: TokenStream):
|
|
147
|
+
with stream.syntax(range=r"\.\.", number=NUMBER_PATTERN):
|
|
148
|
+
lower_bound = self.get_bound(stream)
|
|
149
|
+
range = stream.get("range")
|
|
150
|
+
upper_bound = self.get_bound(stream)
|
|
151
|
+
|
|
152
|
+
return set_location(
|
|
153
|
+
AstMacroRange(min=lower_bound, max=upper_bound),
|
|
154
|
+
lower_bound or range or upper_bound,
|
|
155
|
+
upper_bound or range or lower_bound,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def macro(
|
|
160
|
+
parsers: dict[str, Parser],
|
|
161
|
+
type: str | tuple[str],
|
|
162
|
+
priority=False,
|
|
163
|
+
node_type: type[AstMacroArgument] = AstMacroArgument,
|
|
164
|
+
):
|
|
165
|
+
"""
|
|
166
|
+
Creates the proper AlternativeParser
|
|
167
|
+
|
|
168
|
+
:param parsers: The current set of parsers from mecha
|
|
169
|
+
:type parsers: dict[str, Parser]
|
|
170
|
+
:param type: The parser to create an alternative for
|
|
171
|
+
:type type: str | tuple[str]
|
|
172
|
+
:param priority: Should a macro be checked for before the original parser is used
|
|
173
|
+
:param node_type: The kind of node to be created by the parser
|
|
174
|
+
:type node_type: type[AstMacroArgument]
|
|
175
|
+
"""
|
|
176
|
+
parser_type = type
|
|
177
|
+
if isinstance(type, tuple):
|
|
178
|
+
parser_type = type[0]
|
|
179
|
+
|
|
180
|
+
if not priority:
|
|
181
|
+
return AlternativeParser(
|
|
182
|
+
[parsers[cast(str, parser_type)], MacroParser(type, node_type)]
|
|
183
|
+
)
|
|
184
|
+
return AlternativeParser(
|
|
185
|
+
[MacroParser(type, node_type), parsers[cast(str, parser_type)]]
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def parse_typed_macro(stream: TokenStream):
|
|
190
|
+
"""
|
|
191
|
+
Parses macros with a parser type
|
|
192
|
+
Ex: $(foo: numeric)
|
|
193
|
+
|
|
194
|
+
:param stream: The instance of TokenStream
|
|
195
|
+
:type stream: TokenStream
|
|
196
|
+
"""
|
|
197
|
+
with stream.syntax(
|
|
198
|
+
open_variable=r"\$\(", close_variable=r"\)", parser=r"\w+", colon=r":\s*"
|
|
199
|
+
):
|
|
200
|
+
open_variable = stream.expect("open_variable")
|
|
201
|
+
node: AstMacroLineVariable | AstInterpolation = delegate(
|
|
202
|
+
"macro_line_variable", stream
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
parser = None
|
|
206
|
+
if isinstance(node, AstMacroLineVariable):
|
|
207
|
+
name = node.value
|
|
208
|
+
|
|
209
|
+
if stream.get("colon"):
|
|
210
|
+
parser = stream.expect("parser").value
|
|
211
|
+
|
|
212
|
+
closed_variable = stream.expect("close_variable")
|
|
213
|
+
return set_location(
|
|
214
|
+
AstMacroArgument(name=name, parser=parser), open_variable, closed_variable
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def parse_coordinate(stream: TokenStream):
|
|
219
|
+
"""
|
|
220
|
+
Parses coordinates with support for macros
|
|
221
|
+
|
|
222
|
+
:param stream: The TokenStream instance
|
|
223
|
+
:type stream: TokenStream
|
|
224
|
+
"""
|
|
225
|
+
with stream.syntax(modifier="[~^]"):
|
|
226
|
+
modifier_token = stream.get("modifier")
|
|
227
|
+
|
|
228
|
+
modifier = "absolute"
|
|
229
|
+
|
|
230
|
+
if modifier_token and modifier_token.value == "~":
|
|
231
|
+
modifier = "relative"
|
|
232
|
+
elif modifier_token and modifier_token.value == "^":
|
|
233
|
+
modifier = "local"
|
|
234
|
+
|
|
235
|
+
macro: AstMacroArgument = delegate("typed_macro", stream)
|
|
236
|
+
|
|
237
|
+
if macro.parser and macro.parser != "numeric":
|
|
238
|
+
raise ValueError(
|
|
239
|
+
f"Invalid macro type, received {macro.parser} expected numeric"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
return set_location(
|
|
243
|
+
AstMacroCoordinateArgument(
|
|
244
|
+
name=macro.name, type=modifier, parser="numeric"
|
|
245
|
+
),
|
|
246
|
+
modifier_token or macro.location,
|
|
247
|
+
macro.end_location,
|
|
248
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from nbtlib import Serializer as NbtSerializer
|
|
4
|
+
|
|
5
|
+
from .serialize import serialize_macro
|
|
6
|
+
from .typing import MacroTag
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def apply_patches():
|
|
10
|
+
NbtSerializer.serialize_macro = serialize_macro # type: ignore
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import bolt_expressions.typing
|
|
14
|
+
|
|
15
|
+
convert_tag = bolt_expressions.typing.convert_tag
|
|
16
|
+
|
|
17
|
+
def convert_tag_with_macro(value: Any):
|
|
18
|
+
match value:
|
|
19
|
+
case MacroTag():
|
|
20
|
+
return value
|
|
21
|
+
case _:
|
|
22
|
+
return convert_tag(value)
|
|
23
|
+
|
|
24
|
+
bolt_expressions.typing.convert_tag = convert_tag_with_macro
|
|
25
|
+
|
|
26
|
+
except ImportError:
|
|
27
|
+
...
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from beet import Context
|
|
2
|
+
from bolt import Runtime
|
|
3
|
+
from mecha import AlternativeParser, Mecha, Parser
|
|
4
|
+
|
|
5
|
+
from .ast import (
|
|
6
|
+
AstGreedyWithMacro,
|
|
7
|
+
AstMacroExpression,
|
|
8
|
+
AstMacroNbtArgument,
|
|
9
|
+
AstMessageWithMacro,
|
|
10
|
+
AstNbtValueWithMacro,
|
|
11
|
+
AstStringWithMacro,
|
|
12
|
+
AstWordWithMacro,
|
|
13
|
+
)
|
|
14
|
+
from .codegen import MacroCodegen, ast_to_macro, make_macro_string
|
|
15
|
+
from .parse import (
|
|
16
|
+
MacroNbtPathParser,
|
|
17
|
+
MacroParser,
|
|
18
|
+
MacroRangeParser,
|
|
19
|
+
macro,
|
|
20
|
+
parse_coordinate,
|
|
21
|
+
parse_typed_macro,
|
|
22
|
+
)
|
|
23
|
+
from .patches import apply_patches
|
|
24
|
+
from .serialize import CommandSerializer, MacroConverter, MacroMutator
|
|
25
|
+
|
|
26
|
+
def get_parsers(parsers: dict[str, Parser]):
|
|
27
|
+
parse_nbt: Parser = parsers["nbt"]
|
|
28
|
+
|
|
29
|
+
def make_nbt_parser(parser: Parser):
|
|
30
|
+
return AlternativeParser(
|
|
31
|
+
[MacroParser(("nbt", "string"), AstMacroNbtArgument), parser]
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
new_parsers = {
|
|
35
|
+
"typed_macro": parse_typed_macro,
|
|
36
|
+
"bool": macro(parsers, "bool"),
|
|
37
|
+
"numeric": macro(parsers, "numeric"),
|
|
38
|
+
"coordinate": AlternativeParser([parsers["coordinate"], parse_coordinate]),
|
|
39
|
+
"time": macro(parsers, "time"),
|
|
40
|
+
"word": macro(parsers, "word", priority=True),
|
|
41
|
+
"phrase": macro(parsers, "phrase", priority=True),
|
|
42
|
+
"greedy": macro(parsers, "greedy", priority=True),
|
|
43
|
+
"entity": macro(parsers, "entity", priority=True),
|
|
44
|
+
"nbt": make_nbt_parser(parsers["nbt"]),
|
|
45
|
+
"nbt_compound_entry": make_nbt_parser(parsers["nbt_compound_entry"]),
|
|
46
|
+
"nbt_list_or_array_element": make_nbt_parser(
|
|
47
|
+
parsers["nbt_list_or_array_element"]
|
|
48
|
+
),
|
|
49
|
+
"nbt_compound": make_nbt_parser(parsers["nbt_compound"]),
|
|
50
|
+
"nbt_path": AlternativeParser(
|
|
51
|
+
[parsers["nbt_path"], MacroNbtPathParser(nbt_compound_parser=parse_nbt)]
|
|
52
|
+
),
|
|
53
|
+
"range": AlternativeParser([parsers["range"], MacroRangeParser()]),
|
|
54
|
+
"bolt:literal": macro(parsers, "bolt:literal", node_type=AstMacroExpression),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return new_parsers
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
conversions = {
|
|
61
|
+
"interpolate_phrase": AstStringWithMacro,
|
|
62
|
+
"interpolate_word": AstWordWithMacro,
|
|
63
|
+
"interpolate_greedy": AstGreedyWithMacro,
|
|
64
|
+
"interpolate_nbt": AstNbtValueWithMacro,
|
|
65
|
+
"interpolate_message": AstMessageWithMacro,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def beet_default(ctx: Context):
|
|
70
|
+
apply_patches()
|
|
71
|
+
|
|
72
|
+
mc = ctx.inject(Mecha)
|
|
73
|
+
|
|
74
|
+
mc.spec.parsers.update(get_parsers(mc.spec.parsers))
|
|
75
|
+
mc.serialize.extend(CommandSerializer(spec=mc.spec))
|
|
76
|
+
mc.steps.insert(0, MacroMutator())
|
|
77
|
+
|
|
78
|
+
runtime = ctx.inject(Runtime)
|
|
79
|
+
|
|
80
|
+
runtime.modules.codegen.extend(MacroCodegen())
|
|
81
|
+
runtime.helpers[ast_to_macro.__name__] = ast_to_macro
|
|
82
|
+
runtime.helpers[make_macro_string.__name__] = make_macro_string
|
|
83
|
+
|
|
84
|
+
for conversion, node_type in conversions.items():
|
|
85
|
+
runtime.helpers[conversion] = MacroConverter(
|
|
86
|
+
runtime.helpers[conversion], node_type
|
|
87
|
+
)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any, Callable, List
|
|
3
|
+
|
|
4
|
+
from beet.core.utils import required_field
|
|
5
|
+
from bolt import AstFormatString
|
|
6
|
+
from mecha import (
|
|
7
|
+
AstCommand,
|
|
8
|
+
AstNbtPath,
|
|
9
|
+
AstNbtPathKey,
|
|
10
|
+
AstNode,
|
|
11
|
+
CommandSpec,
|
|
12
|
+
MutatingReducer,
|
|
13
|
+
Visitor,
|
|
14
|
+
rule,
|
|
15
|
+
)
|
|
16
|
+
from mecha.utils import number_to_string
|
|
17
|
+
from nbtlib import Serializer as NbtSerializer
|
|
18
|
+
from tokenstream import set_location
|
|
19
|
+
|
|
20
|
+
from .ast import (
|
|
21
|
+
AstMacroArgument,
|
|
22
|
+
AstMacroCoordinateArgument,
|
|
23
|
+
AstMacroNbtArgument,
|
|
24
|
+
AstMacroNbtPathArgument,
|
|
25
|
+
AstMacroNbtPathKeyArgument,
|
|
26
|
+
AstMacroRange,
|
|
27
|
+
AstMacroStringWrapper,
|
|
28
|
+
)
|
|
29
|
+
from .typing import MacroRepresentation, MacroTag, StringWithMacro
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def serialize_macro(_self: NbtSerializer, tag: MacroTag):
|
|
33
|
+
if tag.parser == "string":
|
|
34
|
+
return f'"$({tag.name})"'
|
|
35
|
+
|
|
36
|
+
return f"$({tag.name})"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class MacroConverter:
|
|
41
|
+
"""
|
|
42
|
+
Used to convert interpolated strings with contain macros to the proper AstXWithMacro nodes
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
base_converter: Callable[[Any, AstNode], AstNode]
|
|
46
|
+
node_type: type
|
|
47
|
+
|
|
48
|
+
def __call__(self, obj: Any, node: AstNode) -> AstNode:
|
|
49
|
+
if isinstance(obj, StringWithMacro):
|
|
50
|
+
return self.node_type.from_value(obj)
|
|
51
|
+
return self.base_converter(obj, node)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class CommandSerializer(Visitor):
|
|
56
|
+
spec: CommandSpec = required_field()
|
|
57
|
+
|
|
58
|
+
@rule(AstCommand)
|
|
59
|
+
def command(self, node: AstCommand, result: list[str]):
|
|
60
|
+
prototype = self.spec.prototypes[node.identifier]
|
|
61
|
+
argument_index = 0
|
|
62
|
+
|
|
63
|
+
sep = ""
|
|
64
|
+
|
|
65
|
+
start_index = 0
|
|
66
|
+
# Scan backwards until we find the start of the current line
|
|
67
|
+
for i in range(len(result) - 1, -1, -1):
|
|
68
|
+
if result[i] == "\n":
|
|
69
|
+
start_index = i + 1
|
|
70
|
+
break
|
|
71
|
+
|
|
72
|
+
for token in prototype.signature:
|
|
73
|
+
result.append(sep)
|
|
74
|
+
sep = " "
|
|
75
|
+
|
|
76
|
+
# If token is a string then we can move on, literals can't contain macros
|
|
77
|
+
if isinstance(token, str):
|
|
78
|
+
result.append(token)
|
|
79
|
+
else:
|
|
80
|
+
argument = node.arguments[argument_index]
|
|
81
|
+
|
|
82
|
+
# Scan the argument for any MacroRepresentations
|
|
83
|
+
for child in argument.walk():
|
|
84
|
+
if isinstance(child, MacroRepresentation):
|
|
85
|
+
result[start_index] = "$"
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
yield argument
|
|
89
|
+
argument_index += 1
|
|
90
|
+
|
|
91
|
+
if result[start_index] == "$":
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
for i in range(start_index, len(result)):
|
|
95
|
+
if result[i] == "$(" and result[i + 2] == ")":
|
|
96
|
+
result[start_index] = "$"
|
|
97
|
+
break
|
|
98
|
+
|
|
99
|
+
def default(self, argument: AstMacroArgument, result: list[str]):
|
|
100
|
+
string = argument.parser == "string"
|
|
101
|
+
if string:
|
|
102
|
+
result.append('"')
|
|
103
|
+
|
|
104
|
+
result.append("$(")
|
|
105
|
+
result.append(argument.name)
|
|
106
|
+
result.append(")")
|
|
107
|
+
|
|
108
|
+
if string:
|
|
109
|
+
result.append('"')
|
|
110
|
+
|
|
111
|
+
@rule(AstMacroArgument, AstMacroNbtPathArgument, AstMacroNbtArgument)
|
|
112
|
+
def macro(self, argument: AstMacroArgument, result: list[str]):
|
|
113
|
+
self.default(argument, result)
|
|
114
|
+
|
|
115
|
+
@rule(AstMacroCoordinateArgument)
|
|
116
|
+
def coordinate(self, argument: AstMacroCoordinateArgument, result: list[str]):
|
|
117
|
+
if argument.type == "local":
|
|
118
|
+
result.append("^")
|
|
119
|
+
elif argument.type == "relative":
|
|
120
|
+
result.append("~")
|
|
121
|
+
|
|
122
|
+
self.default(argument, result)
|
|
123
|
+
|
|
124
|
+
@rule(AstMacroNbtPathKeyArgument)
|
|
125
|
+
def macro_path_key(self, argument: AstMacroNbtPathKeyArgument, result: list[str]):
|
|
126
|
+
self.default(argument, result)
|
|
127
|
+
|
|
128
|
+
@rule(AstNbtPath)
|
|
129
|
+
def nbt_path(self, node: AstNbtPath, result: List[str]):
|
|
130
|
+
sep = ""
|
|
131
|
+
for component in node.components:
|
|
132
|
+
if isinstance(
|
|
133
|
+
component,
|
|
134
|
+
(AstNbtPathKey, AstMacroNbtPathKeyArgument, AstMacroNbtPathArgument),
|
|
135
|
+
):
|
|
136
|
+
result.append(sep)
|
|
137
|
+
sep = "."
|
|
138
|
+
yield component
|
|
139
|
+
|
|
140
|
+
@rule(AstMacroRange)
|
|
141
|
+
def range(self, node: AstMacroRange, result: list[str]):
|
|
142
|
+
if node.min == node.max and node.min is not None:
|
|
143
|
+
if isinstance(node.min, AstMacroArgument):
|
|
144
|
+
yield node.min
|
|
145
|
+
else:
|
|
146
|
+
result.append(number_to_string(node.min))
|
|
147
|
+
else:
|
|
148
|
+
if node.min is not None:
|
|
149
|
+
if isinstance(node.min, AstMacroArgument):
|
|
150
|
+
yield node.min
|
|
151
|
+
else:
|
|
152
|
+
result.append(number_to_string(node.min))
|
|
153
|
+
|
|
154
|
+
result.append("..")
|
|
155
|
+
|
|
156
|
+
if node.max is not None:
|
|
157
|
+
if isinstance(node.max, AstMacroArgument):
|
|
158
|
+
yield node.max
|
|
159
|
+
else:
|
|
160
|
+
result.append(number_to_string(node.max))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@dataclass
|
|
164
|
+
class MacroMutator(MutatingReducer):
|
|
165
|
+
@rule(AstFormatString)
|
|
166
|
+
def format_string(self, node: AstFormatString):
|
|
167
|
+
if any(map(lambda v: isinstance(v, AstMacroArgument), node.values)):
|
|
168
|
+
return set_location(
|
|
169
|
+
AstMacroStringWrapper(child=node), node.location, node.end_location
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
return node
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from beet.core.utils import required_field
|
|
4
|
+
from nbtlib import Base
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MacroRepresentation: ...
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class StringWithMacro(str): ...
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class MacroTag(Base):
|
|
15
|
+
name: str = required_field()
|
|
16
|
+
parser: str | None = required_field()
|
|
17
|
+
|
|
18
|
+
def __post_init__(self):
|
|
19
|
+
self.serializer = "macro"
|
|
20
|
+
|
|
21
|
+
def __str__(self):
|
|
22
|
+
return f"$({self.name})"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: bolt-native-macros
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Dist: beet>=0.112.2
|
|
6
|
+
Requires-Dist: bolt>=0.49.2
|
|
7
|
+
Requires-Dist: mecha>=0.101.0
|
|
8
|
+
Requires-Dist: bolt-expressions>=0.17.0 ; extra == 'bolt-expressions'
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Provides-Extra: bolt-expressions
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
bolt_native_macros/__init__.py,sha256=WYzqGk2ygNeMaqQ-tCuLnIm4fQAaugHDLpFpnUeJ_O4,72
|
|
2
|
+
bolt_native_macros/ast.py,sha256=ubKxujWN6lUoGBNbKfZRNcRaN4ecpf9b9euGiEXxPPs,2810
|
|
3
|
+
bolt_native_macros/codegen.py,sha256=9f-3MEcj03mgx3xMNlSMzeYYVwXo04YoMF7QGkTMvwM,1738
|
|
4
|
+
bolt_native_macros/parse.py,sha256=e-9zoUcBopu6M3bjBgBGd0pycLpSvRTaTPgMiEpbbrA,8073
|
|
5
|
+
bolt_native_macros/patches.py,sha256=GMJtdQqkL1waP8wu4QTamhw2R7Hm3CGnogp4xRNWNQo,656
|
|
6
|
+
bolt_native_macros/plugin.py,sha256=Om3Wx6xfIKiiNNNqien9CNadMmOshpONC3a9zacwPCU,2857
|
|
7
|
+
bolt_native_macros/serialize.py,sha256=Lsf7oOl3TZyjMG-gpm-YMmNUlCtP3uiCqVfUO7xNN10,5160
|
|
8
|
+
bolt_native_macros/typing.py,sha256=DxUViLB9xFIYNbUhjxc7jFCNV-r2yETyQ5ZNTG0V2F4,400
|
|
9
|
+
bolt_native_macros-0.2.3.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
|
|
10
|
+
bolt_native_macros-0.2.3.dist-info/METADATA,sha256=CEGmEXEzSZ7PyPYms4s-uejXWn7Pwf46deHMeLxPiEE,352
|
|
11
|
+
bolt_native_macros-0.2.3.dist-info/RECORD,,
|