bolt_compute 0.0.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.
@@ -0,0 +1,100 @@
1
+ Metadata-Version: 2.5
2
+ Name: bolt_compute
3
+ Version: 0.0.3
4
+ Summary: Add your description here
5
+ Requires-Python: >=3.14
6
+ Requires-Dist: beet>=0.116.0
7
+ Requires-Dist: bolt>=0.50.1
8
+ Description-Content-Type: text/markdown
9
+
10
+ # Bolt Compute
11
+
12
+ A [beet](https://github.com/mcbeet/beet) plugin that allows to use expressions instead of inline [number providers](https://minecraft.wiki/w/Number_provider).
13
+
14
+
15
+ ## Installation
16
+
17
+ In a beet project, use `uv add bolt_compute` and add the module in the require phase in your beet config:
18
+
19
+ ```yaml
20
+ require:
21
+ - bolt
22
+ - bolt_compute # <-- Add this line
23
+ pipeline:
24
+ - mecha
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ Any place where the `compute` command/subcommand is used, you can use the `bolt` subcommand to use an expression instead of a number provider. For example, instead of:
30
+
31
+ ```mcfunction
32
+ compute default {type:"minecraft:sum",operands:[1,1]}
33
+ data modify storage namespace:path path set compute default {type:"minecraft:sum",operands:[1,1]}
34
+ ```
35
+ You can use:
36
+
37
+ ```mcfunction
38
+ compute bolt (1 + 1)
39
+ data modify storage namespace:path path set compute bolt (1 + 1)
40
+ ```
41
+
42
+ ### Literals values
43
+
44
+ Bolt compute supports the following literal values:
45
+ ```mcfunction
46
+ compute bolt (storage namespace:path path) # compute default {type:"minecraft:storage",storage:"namespace:path",path:"path"}
47
+
48
+ x = 1 # bolt variable assignment
49
+ compute bolt (x + 1) # compute default {type:"minecraft:sum",operands:[1,1]}
50
+
51
+ y = "namespace:path_to_number_provider"
52
+ compute bolt (y * y) # compute default {type:"minecraft:product",operands:["namespace:path_to_number_provider","namespace:path_to_number_provider"]}
53
+
54
+ ```
55
+
56
+ ### Operators
57
+
58
+ `+`, `-`, `*`, are supported, as well as parentheses for grouping.
59
+
60
+ ### Functions
61
+
62
+ Built-in functions are supported, such as :
63
+ - `average` expect a list using square brackets, e.g. `average([1,2,3])`
64
+ - `binomial` expect N and P, can be expressions, e.g. `binomial(10, 0.5)`
65
+ - `conditional` currently not implemented
66
+ - `maximum` expect a list using square brackets, e.g. `maximum([1,2,3])`
67
+ - `minimum` expect a list using square brackets, e.g. `minimum([1,2,3])`
68
+ - `uniform` expect two number providers, can be expressions, e.g. `uniform(1, 10)`
69
+ - `sum` expect a list using square brackets, e.g. `sum([1,2,3])`
70
+ - `product` expect a list using square brackets, e.g. `product([1,2,3])`
71
+
72
+ ### Multiline expressions
73
+
74
+ Expressions can be multiline, for example:
75
+
76
+ ```mcfunction
77
+ x = 1
78
+ y = 2
79
+ compute bolt (
80
+ x + y
81
+ )
82
+ ```
83
+
84
+ ### Full example
85
+
86
+ ```mcfunction
87
+
88
+ x = "namespace:path_to_number_provider"
89
+ y = 256*256
90
+
91
+ data modify storage example:main result_bolt set compute bolt (
92
+ (storage example:main prod1)*(storage example:main prod2)*(storage example:main prod3)*2
93
+ *
94
+ 2485*52+
95
+ maximum([0,1,(storage example:main prod2),1])+(143)-
96
+ (y*x) + x
97
+ )
98
+ ```
99
+
100
+ More examples can be found in the [examples](examples) folder.
@@ -0,0 +1,4 @@
1
+ bolt_compute.py,sha256=U20K8rZsyDkTgUqnRCuXTcmoLqHs96MZthy8irmLzzQ,17718
2
+ bolt_compute-0.0.3.dist-info/METADATA,sha256=i0-hn6UyBplikmiHOd_5bTNy6Om9y7XXID2xSJgBQ_4,2821
3
+ bolt_compute-0.0.3.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
4
+ bolt_compute-0.0.3.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
bolt_compute.py ADDED
@@ -0,0 +1,502 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, Callable, Generator, Literal, Optional, Self, Type, TypedDict, overload
3
+
4
+ from beet import Context
5
+ from beet.core.utils import JsonDict, required_field
6
+ from bolt import AstCall, AstFormatString, AstIdentifier, AstValue
7
+ from mecha import AbstractNode, AstNbtPath, AstNbtPathKey, AstNbtPathSubscript, AstNode, AstNumber, AstResourceLocation, Mecha, CommandTree, MultilineParser, NbtPathParser, Rule, delegate, rule
8
+ from mecha.utils import QuoteHelper, number_to_string
9
+ from bolt.pattern import STRING_PATTERN, RESOURCE_LOCATION_PATTERN
10
+ from tokenstream import TokenStream, Token, set_location, InvalidSyntax
11
+
12
+ FUNCTION_OVERRIDES = [
13
+ "average",
14
+ "binomial",
15
+ "conditional",
16
+ "maximum",
17
+ "minimum",
18
+ "uniform",
19
+ "sum",
20
+ "product",
21
+ ]
22
+
23
+ def iter_compute_tree(tree: CommandTree):
24
+ if tree.children:
25
+ compute = tree.children["compute"]
26
+ if compute.children:
27
+ yield compute
28
+
29
+
30
+ data = tree.children["data"]
31
+ if data.children:
32
+ modify = data.children["modify"]
33
+ if modify.children:
34
+ childs = [
35
+ "block",
36
+ "entity",
37
+ "storage",
38
+ ]
39
+ for c in childs:
40
+ child = modify.children[c]
41
+ if child.children:
42
+ target = child.children["target"]
43
+ if target.children:
44
+ targetPath = target.children["targetPath"]
45
+ if targetPath.children:
46
+ for o in [
47
+ "append",
48
+ "merge",
49
+ "prepend",
50
+ "set",
51
+ ]:
52
+ operation = targetPath.children[o]
53
+ if operation.children:
54
+ compute = operation.children["compute"]
55
+ yield compute
56
+ insert = targetPath.children["insert"]
57
+ if insert.children:
58
+ index = insert.children["index"]
59
+ if index.children:
60
+ compute = index.children["compute"]
61
+ yield compute
62
+
63
+ @dataclass
64
+ class MutableDepth:
65
+ value: int
66
+
67
+ @dataclass(frozen=True, slots=True)
68
+ class AstComputeStorage(AstNode):
69
+ """Ast bolt compute storage node."""
70
+ storage: AstComputeResourceLocation = required_field()
71
+ path: AstNbtPath = required_field()
72
+ depth: MutableDepth = required_field()
73
+
74
+ @classmethod
75
+ def from_value(
76
+ cls,
77
+ storage: AstComputeResourceLocation,
78
+ path: AstNbtPath,
79
+ depth: int,
80
+ ) -> AstComputeStorage:
81
+ """Return a bool node from the given value."""
82
+ return AstComputeStorage(storage=storage, path=path, depth=MutableDepth(depth))
83
+
84
+ @dataclass(frozen=True, slots=True)
85
+ class AstComputeBoltValue(AstNode):
86
+ value: AstNode = required_field()
87
+ depth: MutableDepth = required_field()
88
+
89
+ @classmethod
90
+ def from_value(
91
+ cls,
92
+ value: AstNode,
93
+ depth: int,
94
+ ) -> Self:
95
+ return cls(value=value, depth=MutableDepth(depth))
96
+
97
+ @dataclass(frozen=True, slots=True)
98
+ class AstComputeListCall(AstNode):
99
+ type: str = required_field()
100
+ operands: list[ValueType] = required_field()
101
+ depth: MutableDepth = required_field()
102
+
103
+ @classmethod
104
+ def from_value(
105
+ cls,
106
+ type: str,
107
+ operands: list[ValueType],
108
+ depth: int,
109
+ ) -> Self:
110
+ """Return a bool node from the given value."""
111
+ return cls(type=type, operands=operands, depth=MutableDepth(depth))
112
+
113
+ @dataclass(frozen=True, slots=True)
114
+ class AstComputeBinomial(AstNode):
115
+ n: ValueType = required_field()
116
+ p: ValueType = required_field()
117
+ depth: MutableDepth = required_field()
118
+
119
+ @classmethod
120
+ def from_value(
121
+ cls,
122
+ n: ValueType,
123
+ p: ValueType,
124
+ depth: int,
125
+ ) -> Self:
126
+ return cls(n=n, p=p, depth=MutableDepth(depth))
127
+
128
+ @dataclass(frozen=True, slots=True)
129
+ class AstComputeUniform(AstNode):
130
+ minimum: ValueType = required_field()
131
+ maximum: ValueType = required_field()
132
+ depth: MutableDepth = required_field()
133
+
134
+ @classmethod
135
+ def from_value(
136
+ cls,
137
+ minimum: ValueType,
138
+ maximum: ValueType,
139
+ depth: int,
140
+ ) -> Self:
141
+ return cls(minimum=minimum, maximum=maximum, depth=MutableDepth(depth))
142
+
143
+
144
+
145
+
146
+ @dataclass(frozen=True, slots=True)
147
+ class AstComputeNumber(AstNode):
148
+ value: AstNumber = required_field()
149
+ depth: MutableDepth = required_field()
150
+
151
+ @classmethod
152
+ def from_value(
153
+ cls,
154
+ value: Any,
155
+ depth: int,
156
+ ) -> Self:
157
+ """Return a bool node from the given value."""
158
+ return cls(value=AstNumber.from_value(value), depth=MutableDepth(depth))
159
+
160
+
161
+
162
+ type AstComputeSource = AstComputeStorage # future AstScoreStorage
163
+
164
+ type ValueType = AstComputeOperation | AstComputeResourceLocation | AstComputeNumber | AstComputeSource | AstComputeBoltValue
165
+ type Operation = Literal["+", "-", "/", "*"]
166
+
167
+ @dataclass(frozen=True, slots=True)
168
+ class AstBoltComputeRoot(AstNode):
169
+ children: AstComputeOperation = required_field()
170
+
171
+ @dataclass(frozen=True, slots=True)
172
+ class AstComputeResourceLocation(AstNode):
173
+ resource_location: AstResourceLocation = required_field()
174
+ depth: MutableDepth = required_field()
175
+
176
+ @classmethod
177
+ def from_value(cls, value: Any, depth: int) -> "AstComputeResourceLocation":
178
+ return AstComputeResourceLocation(resource_location=AstResourceLocation.from_value(value), depth=MutableDepth(depth))
179
+
180
+ @dataclass(frozen=True, slots=True)
181
+ class AstComputeOperation(AstNode):
182
+ """Ast bolt compute node."""
183
+
184
+ lvalue: ValueType = required_field()
185
+ operation: Optional[Operation] = required_field()
186
+ rvalue: Optional[ValueType] = required_field()
187
+
188
+ depth: MutableDepth = required_field()
189
+
190
+ parser = "bolt_compute:parse_operation"
191
+
192
+ @classmethod
193
+ def from_value(
194
+ cls,
195
+ lvalue: ValueType,
196
+ operation: Optional[Operation] = None,
197
+ rvalue: Optional[ValueType] = None,
198
+ depth: int = 0,
199
+ ) -> AstComputeOperation:
200
+ """Return a bool node from the given value."""
201
+ return AstComputeOperation(lvalue=lvalue, operation=operation, rvalue=rvalue, depth=MutableDepth(depth))
202
+
203
+
204
+ def parse_operation(stream: TokenStream, depth: int) -> AstComputeOperation:
205
+ """Parse operations with correct precedence: additive (lower) then multiplicative (higher)."""
206
+ return parse_additive(stream, depth)
207
+
208
+
209
+ def parse_additive(stream: TokenStream, depth: int) -> AstComputeOperation:
210
+ """Parse additive operations (+, -) - lowest precedence."""
211
+ lvalue = parse_multiplicative(stream, depth)
212
+
213
+ while True:
214
+ with stream.alternative():
215
+ token = stream.expect("additive")
216
+ rvalue = parse_multiplicative(stream, depth+1)
217
+ op: Operation = token.value # pyright: ignore[reportAssignmentType]
218
+ lvalue = AstComputeOperation.from_value(lvalue, op, rvalue, depth=depth)
219
+ lvalue.lvalue.depth.value +=1
220
+ continue
221
+ break
222
+
223
+ return lvalue
224
+
225
+
226
+ def parse_multiplicative(stream: TokenStream, depth: int) -> AstComputeOperation:
227
+ """Parse multiplicative operations (*, /) - highest precedence."""
228
+ lvalue = parse_primary(stream, depth)
229
+
230
+ while True:
231
+ with stream.alternative():
232
+ token = stream.expect("multiplicative")
233
+ rvalue = parse_primary(stream, depth+1)
234
+ op: Operation = token.value # pyright: ignore[reportAssignmentType]
235
+ lvalue = AstComputeOperation.from_value(lvalue, op, rvalue, depth=depth)
236
+ lvalue.lvalue.depth.value +=1
237
+ continue
238
+ break
239
+
240
+ return lvalue
241
+
242
+
243
+ def parse_primary(stream: TokenStream, depth: int) -> ValueType:
244
+ """Parse primary expressions (literals, parenthesized expressions, function calls)."""
245
+ with stream.checkpoint() as commit:
246
+ stream.expect("oparent")
247
+ result = parse_additive(stream, depth=depth)
248
+ stream.expect("cparent")
249
+ commit()
250
+ return result
251
+
252
+ return parse_literal(stream, depth=depth)
253
+
254
+ def parse_list(stream: TokenStream, depth: int):
255
+ """Parse a comma-separated list of expressions inside brackets."""
256
+ stream.expect("obracket")
257
+ values: list[ValueType] = []
258
+ while True:
259
+ with stream.checkpoint() as commit:
260
+ stream.expect("cbracket")
261
+ commit()
262
+ break
263
+ values.append(parse_operation(stream, depth=depth+1))
264
+ follow = stream.expect_any("comma", "cbracket")
265
+ match follow:
266
+ case Token("cbracket"):
267
+ break
268
+ case Token("comma"):
269
+ ...
270
+ return values
271
+
272
+
273
+ def parse_literal(stream: TokenStream, depth: int = 0) -> ValueType:
274
+ bolt_expression_parser = delegate("bolt:primary")
275
+ with stream.checkpoint() as commit:
276
+ bolt_node: AstNode = bolt_expression_parser(stream)
277
+ # parse and return value type
278
+ if isinstance(bolt_node, AstValue):
279
+ if isinstance(bolt_node.value, (float, int)):
280
+ commit()
281
+ return AstComputeNumber.from_value(bolt_node.value, depth=depth)
282
+ elif isinstance(bolt_node.value, str):
283
+ commit()
284
+ return AstComputeResourceLocation.from_value(bolt_node.value, depth=depth)
285
+ elif isinstance(bolt_node, AstIdentifier):
286
+ commit()
287
+ return AstComputeBoltValue.from_value(bolt_node, depth=depth)
288
+ elif isinstance(bolt_node, AstFormatString):
289
+ commit()
290
+ return AstComputeBoltValue.from_value(bolt_node, depth=depth)
291
+ elif isinstance(bolt_node, AstCall):
292
+ if isinstance(bolt_node.value, AstIdentifier) and bolt_node.value.value in FUNCTION_OVERRIDES:
293
+ raise InvalidSyntax(f"Python built-in sum collide with bolt_compute sum")
294
+ commit()
295
+ return AstComputeBoltValue.from_value(bolt_node, depth=depth)
296
+
297
+ raise NotImplementedError(bolt_node)
298
+
299
+ stream.crop()
300
+ token = stream.expect_any("oparent", "number", "quotes", "storage", "call")
301
+ match token:
302
+ case Token("oparent"):
303
+ return parse_operation(stream, depth=depth+1)
304
+ case Token("number"):
305
+ return AstComputeNumber.from_value(token.value, depth=depth)
306
+ case Token("quotes"):
307
+ res: AstResourceLocation = delegate("resource_location")(stream)
308
+ stream.expect("quotes")
309
+ return AstComputeResourceLocation.from_value(res, depth=depth)
310
+ case Token("storage"):
311
+ storage: AstComputeResourceLocation = delegate("resource_location")(stream)
312
+ parser = delegate("nbt_path")
313
+ path: AstNbtPath = parser(stream)
314
+ return AstComputeStorage.from_value(storage, path, depth=depth)
315
+ case Token("call"):
316
+ if token.value in ["maximum", "minimum", "average", "sum", "product"]:
317
+ stream.expect("oparent")
318
+ values = parse_list(stream, depth+1)
319
+ if len(values) == 0:
320
+ raise InvalidSyntax(f"Function {token.value} require at least one argument")
321
+ stream.expect("cparent")
322
+ return AstComputeListCall.from_value(token.value, values, depth)
323
+ elif token.value == "binomial":
324
+ stream.expect("oparent")
325
+ N = parse_literal(stream, depth=depth+1)
326
+ stream.expect("comma")
327
+ P = parse_literal(stream, depth=depth+1)
328
+ cparent, comma = stream.expect("cparent", "comma")
329
+ if comma: stream.expect("cparent")
330
+ return AstComputeBinomial.from_value(N, P, depth)
331
+ elif token.value == "uniform":
332
+ stream.expect("oparent")
333
+ minimum = parse_literal(stream, depth=depth+1)
334
+ stream.expect("comma")
335
+ maximum = parse_literal(stream, depth=depth+1)
336
+ cparent, comma = stream.expect("cparent", "comma")
337
+ if comma: stream.expect("cparent")
338
+ return AstComputeUniform.from_value(minimum, maximum, depth)
339
+
340
+ raise NotImplementedError(token.value)
341
+
342
+ raise NotImplementedError("UNREACHABLE")
343
+
344
+
345
+
346
+ def operation_parser(stream: TokenStream):
347
+ """Parse operation."""
348
+ with stream.syntax(
349
+ bolt="bolt"
350
+ ):
351
+ stream.expect("bolt")
352
+ with stream.syntax(
353
+ oparent=r"\(",
354
+ cparent=r"\)",
355
+ obracket=r"\[",
356
+ cbracket=r"\]",
357
+ comma=r",",
358
+ equal=r"=",
359
+ additive=r"\+|\-",
360
+ multiplicative=r"\*|\/",
361
+ number=r"[+-]?([0-9]*[.])?[0-9]+",
362
+ storage=r"storage",
363
+ call="|".join(FUNCTION_OVERRIDES),
364
+ quotes=r'"',
365
+ resource=RESOURCE_LOCATION_PATTERN,
366
+ name=r'[a-z]([a-z0-9]+)?'
367
+ ):
368
+ stream.expect("oparent")
369
+ operation = parse_operation(stream, depth=0)
370
+ stream.expect("cparent", "end")
371
+ return AstBoltComputeRoot(children=operation)
372
+
373
+
374
+
375
+ @rule(AstComputeOperation)
376
+ def serialize_operation(node: AstComputeOperation, result: list[str]):
377
+ if node.lvalue and not node.rvalue:
378
+ assert node.operation is None
379
+ yield node.lvalue
380
+ elif node.lvalue and node.rvalue:
381
+ assert node.operation is not None
382
+ match node.operation:
383
+ case "+":
384
+ result.append('{type:"minecraft:sum",operands:[')
385
+ yield node.lvalue
386
+ result.append(',')
387
+ yield node.rvalue
388
+ result.append(']}')
389
+ case "-":
390
+ result.append('{type:"minecraft:sum",operands:[')
391
+ yield node.lvalue
392
+ result.append(',')
393
+ result.append('{type:"minecraft:product",operands:[-1')
394
+ result.append(',')
395
+ yield node.rvalue
396
+ result.append(']}')
397
+ result.append(']}')
398
+ case "*":
399
+ result.append('{type:"minecraft:product",operands:[')
400
+ yield node.lvalue
401
+ result.append(',')
402
+ yield node.rvalue
403
+ result.append(']}')
404
+ case _:
405
+ raise NotImplementedError()
406
+
407
+ @rule(AstBoltComputeRoot)
408
+ def serialize_root(node: AstBoltComputeRoot, result: list[str]):
409
+ result.append("default")
410
+ result.append(" ")
411
+ yield node.children
412
+
413
+ @rule(AstComputeResourceLocation)
414
+ def serialize_resource_location(node: AstComputeResourceLocation, result: list[str]):
415
+ if node.depth.value != 0: result.append('"')
416
+ result.append(node.resource_location.get_canonical_value())
417
+ if node.depth.value != 0: result.append('"')
418
+
419
+ @rule(AstComputeStorage)
420
+ def serialize_storage(node: AstComputeStorage, result: list[str]):
421
+ result.append('{type:"minecraft:storage",storage:"')
422
+ yield node.storage
423
+ result.append('",path:"')
424
+ yield node.path
425
+ result.append('"}')
426
+
427
+
428
+ @rule(AstComputeBoltValue)
429
+ def serialize_bolt_value(node: AstComputeBoltValue, result: list[str]):
430
+ if isinstance(node.value, (float, int)):
431
+ yield AstComputeNumber.from_value(node.value, node.depth.value)
432
+ elif isinstance(node.value, str):
433
+ a = AstComputeResourceLocation.from_value(node.value, node.depth.value)
434
+ yield a
435
+
436
+
437
+ @rule(AstComputeListCall)
438
+ def serialize_list_call(node: AstComputeListCall, result: list[str]):
439
+ result.append('{type:"minecraft:')
440
+ result.append(node.type)
441
+ result.append('",operands:[')
442
+ sep = ""
443
+ for children in node.operands:
444
+ result.append(sep)
445
+ sep = ","
446
+ yield children
447
+ result.append(']}')
448
+
449
+
450
+
451
+
452
+ @rule(AstComputeNumber)
453
+ def serialize_compute_number(node: AstComputeNumber, result: list[str]):
454
+ if node.depth.value == 0: result.append('{type:"minecraft:constant",value:')
455
+ yield node.value
456
+ if node.depth.value == 0: result.append('}')
457
+
458
+ @rule(AstComputeBinomial)
459
+ def serialize_compute_binomial(node: AstComputeBinomial, result: list[str]):
460
+ result.append('{type:"minecraft:binomial",n:')
461
+ yield node.n
462
+ result.append(',p:')
463
+ yield node.p
464
+ result.append('}')
465
+
466
+ @rule(AstComputeUniform)
467
+ def serialize_compute_uniform(node: AstComputeUniform, result: list[str]):
468
+ result.append('{type:"minecraft:uniform",min:')
469
+ yield node.minimum
470
+ result.append(',max:')
471
+ yield node.maximum
472
+ result.append('}')
473
+
474
+ def beet_default(ctx: Context):
475
+ mc = ctx.inject(Mecha)
476
+ mc.spec.parsers["command:argument:minecraft:number_provider"] = MultilineParser(delegate("resource_location_or_nbt"))
477
+ rules = [
478
+ serialize_operation,
479
+ serialize_resource_location,
480
+ serialize_root,
481
+ serialize_storage,
482
+ serialize_bolt_value,
483
+ serialize_compute_number,
484
+ serialize_list_call,
485
+ serialize_compute_binomial,
486
+ serialize_compute_uniform,
487
+ ]
488
+ for r in rules:
489
+ mc.serialize.add_rule(r)
490
+
491
+ for compute in iter_compute_tree(mc.spec.tree):
492
+ if compute.children:
493
+ compute.children["bolt"] = CommandTree(**{
494
+ "type": "argument",
495
+ "executable": True,
496
+ "parser": "bolt_compute:operation_parser"
497
+ })
498
+
499
+ mc.spec.parsers["command:argument:bolt_compute:operation_parser"] = MultilineParser(operation_parser)
500
+
501
+
502
+ mc.spec.update()