minecraft-datapack-language 15.4.31__py3-none-any.whl → 15.4.33__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.
- minecraft_datapack_language/_version.py +2 -2
- minecraft_datapack_language/cli.py +22 -7
- minecraft_datapack_language/mdl_compiler.py +322 -58
- {minecraft_datapack_language-15.4.31.dist-info → minecraft_datapack_language-15.4.33.dist-info}/METADATA +1 -2
- {minecraft_datapack_language-15.4.31.dist-info → minecraft_datapack_language-15.4.33.dist-info}/RECORD +9 -9
- {minecraft_datapack_language-15.4.31.dist-info → minecraft_datapack_language-15.4.33.dist-info}/WHEEL +0 -0
- {minecraft_datapack_language-15.4.31.dist-info → minecraft_datapack_language-15.4.33.dist-info}/entry_points.txt +0 -0
- {minecraft_datapack_language-15.4.31.dist-info → minecraft_datapack_language-15.4.33.dist-info}/licenses/LICENSE +0 -0
- {minecraft_datapack_language-15.4.31.dist-info → minecraft_datapack_language-15.4.33.dist-info}/top_level.txt +0 -0
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
28
28
|
commit_id: COMMIT_ID
|
29
29
|
__commit_id__: COMMIT_ID
|
30
30
|
|
31
|
-
__version__ = version = '15.4.
|
32
|
-
__version_tuple__ = version_tuple = (15, 4,
|
31
|
+
__version__ = version = '15.4.33'
|
32
|
+
__version_tuple__ = version_tuple = (15, 4, 33)
|
33
33
|
|
34
34
|
__commit_id__ = commit_id = None
|
@@ -26,6 +26,8 @@ Examples:
|
|
26
26
|
mdl new my_project # Create a new project
|
27
27
|
"""
|
28
28
|
)
|
29
|
+
# Global options
|
30
|
+
parser.add_argument('--version', action='store_true', help='Show version and exit')
|
29
31
|
|
30
32
|
subparsers = parser.add_subparsers(dest='command', help='Available commands')
|
31
33
|
|
@@ -34,6 +36,7 @@ Examples:
|
|
34
36
|
build_parser.add_argument('--mdl', required=True, help='MDL file(s) or directory to build')
|
35
37
|
build_parser.add_argument('-o', '--output', required=True, help='Output directory for the datapack')
|
36
38
|
build_parser.add_argument('--verbose', action='store_true', help='Show detailed output')
|
39
|
+
build_parser.add_argument('--wrapper', help='Optional wrapper directory name for the datapack output')
|
37
40
|
|
38
41
|
# Check command
|
39
42
|
check_parser = subparsers.add_parser('check', help='Check MDL files for syntax errors')
|
@@ -45,9 +48,19 @@ Examples:
|
|
45
48
|
new_parser.add_argument('project_name', help='Name of the new project')
|
46
49
|
new_parser.add_argument('--pack-name', help='Custom name for the datapack')
|
47
50
|
new_parser.add_argument('--pack-format', type=int, default=82, help='Pack format number (default: 82)')
|
51
|
+
new_parser.add_argument('--output', help='Directory to create the project in (defaults to current directory)')
|
48
52
|
|
49
53
|
args = parser.parse_args()
|
50
54
|
|
55
|
+
if args.version and not args.command:
|
56
|
+
# Print version and exit
|
57
|
+
try:
|
58
|
+
from . import __version__
|
59
|
+
except Exception:
|
60
|
+
__version__ = "0.0.0"
|
61
|
+
print(__version__)
|
62
|
+
return 0
|
63
|
+
|
51
64
|
if not args.command:
|
52
65
|
parser.print_help()
|
53
66
|
return 1
|
@@ -133,6 +146,7 @@ def build_command(args):
|
|
133
146
|
print(f"Compiling to {output_dir}...")
|
134
147
|
|
135
148
|
compiler = MDLCompiler()
|
149
|
+
# Note: --wrapper is currently accepted for compatibility but not required by compiler
|
136
150
|
output_path = compiler.compile(final_ast, str(output_dir))
|
137
151
|
|
138
152
|
print(f"Successfully built datapack: {output_path}")
|
@@ -193,9 +207,10 @@ def new_command(args):
|
|
193
207
|
project_name = args.project_name
|
194
208
|
pack_name = args.pack_name or project_name
|
195
209
|
pack_format = args.pack_format
|
210
|
+
base_dir = Path(args.output) if getattr(args, 'output', None) else Path('.')
|
211
|
+
project_dir = base_dir / project_name
|
196
212
|
|
197
213
|
# Create project directory
|
198
|
-
project_dir = Path(project_name)
|
199
214
|
if project_dir.exists():
|
200
215
|
print(f"Error: Project directory '{project_name}' already exists")
|
201
216
|
return 1
|
@@ -213,7 +228,7 @@ var num counter<@s> = 0;
|
|
213
228
|
var num global_timer<@a> = 0;
|
214
229
|
|
215
230
|
// Main function
|
216
|
-
function {project_name}:main
|
231
|
+
function {project_name}:main {{
|
217
232
|
say "Hello from {project_name}!";
|
218
233
|
|
219
234
|
// Variable example
|
@@ -228,13 +243,13 @@ function {project_name}:main<@s> {{
|
|
228
243
|
}}
|
229
244
|
}}
|
230
245
|
|
231
|
-
//
|
232
|
-
function {project_name}:
|
233
|
-
say "Datapack
|
246
|
+
// Init function (avoid reserved names like 'load' or 'tick')
|
247
|
+
function {project_name}:init {{
|
248
|
+
say "Datapack initialized successfully!";
|
234
249
|
}}
|
235
250
|
|
236
251
|
// Hook to run on load
|
237
|
-
on_load {project_name}:
|
252
|
+
on_load {project_name}:init;
|
238
253
|
'''
|
239
254
|
|
240
255
|
with open(mdl_file, 'w', encoding='utf-8') as f:
|
@@ -281,7 +296,7 @@ For more information, visit: https://www.mcmdl.com
|
|
281
296
|
with open(readme_file, 'w', encoding='utf-8') as f:
|
282
297
|
f.write(readme_content)
|
283
298
|
|
284
|
-
print(f"Created new MDL project: {
|
299
|
+
print(f"Created new MDL project: {project_dir}/")
|
285
300
|
print(f" - {mdl_file}")
|
286
301
|
print(f" - {readme_file}")
|
287
302
|
print(f"\nNext steps:")
|
@@ -16,6 +16,7 @@ from .ast_nodes import (
|
|
16
16
|
)
|
17
17
|
from .dir_map import get_dir_map, DirMap
|
18
18
|
from .mdl_errors import MDLCompilerError
|
19
|
+
from .mdl_lexer import TokenType
|
19
20
|
|
20
21
|
|
21
22
|
class MDLCompiler:
|
@@ -147,12 +148,23 @@ class MDLCompiler:
|
|
147
148
|
lines.append(f"# Scope: {func.scope}")
|
148
149
|
lines.append("")
|
149
150
|
|
151
|
+
# Reset temporary commands for this function
|
152
|
+
if hasattr(self, 'temp_commands'):
|
153
|
+
self.temp_commands = []
|
154
|
+
|
150
155
|
# Generate commands from function body
|
151
156
|
for statement in func.body:
|
152
157
|
cmd = self._statement_to_command(statement)
|
153
158
|
if cmd:
|
154
159
|
lines.append(cmd)
|
155
160
|
|
161
|
+
# Add any temporary commands that were generated during compilation
|
162
|
+
if hasattr(self, 'temp_commands') and self.temp_commands:
|
163
|
+
lines.append("")
|
164
|
+
lines.append("# Temporary variable operations:")
|
165
|
+
for temp_cmd in self.temp_commands:
|
166
|
+
lines.append(temp_cmd)
|
167
|
+
|
156
168
|
return "\n".join(lines)
|
157
169
|
|
158
170
|
def _compile_hooks(self, hooks: List[HookDeclaration], namespace_dir: Path):
|
@@ -290,9 +302,20 @@ class MDLCompiler:
|
|
290
302
|
def _variable_assignment_to_command(self, assignment: VariableAssignment) -> str:
|
291
303
|
"""Convert variable assignment to scoreboard command."""
|
292
304
|
objective = self.variables.get(assignment.name, assignment.name)
|
293
|
-
value = self._expression_to_value(assignment.value)
|
294
305
|
scope = assignment.scope.strip("<>")
|
295
|
-
|
306
|
+
|
307
|
+
# Check if the value is a complex expression
|
308
|
+
if isinstance(assignment.value, BinaryExpression):
|
309
|
+
# Complex expression - use temporary variable approach
|
310
|
+
temp_var = self._generate_temp_variable_name()
|
311
|
+
self._compile_expression_to_temp(assignment.value, temp_var)
|
312
|
+
|
313
|
+
# Return the command to set the target variable from the temp
|
314
|
+
return f"scoreboard players operation {scope} {objective} = @s {temp_var}"
|
315
|
+
else:
|
316
|
+
# Simple value - use direct assignment
|
317
|
+
value = self._expression_to_value(assignment.value)
|
318
|
+
return f"scoreboard players set {scope} {objective} {value}"
|
296
319
|
|
297
320
|
def _say_command_to_command(self, say: SayCommand) -> str:
|
298
321
|
"""Convert say command to tellraw command with JSON formatting."""
|
@@ -348,94 +371,172 @@ class MDLCompiler:
|
|
348
371
|
return f'tellraw @a {first_part}'
|
349
372
|
|
350
373
|
def _if_statement_to_command(self, if_stmt: IfStatement) -> str:
|
351
|
-
"""Convert if statement to
|
352
|
-
condition = self.
|
353
|
-
lines = [
|
374
|
+
"""Convert if statement to proper Minecraft execute if commands."""
|
375
|
+
condition, invert_then = self._build_condition(if_stmt.condition)
|
376
|
+
lines = []
|
354
377
|
|
355
|
-
#
|
378
|
+
# Prepare function name for the then branch
|
379
|
+
if_function_name = self._generate_if_function_name()
|
380
|
+
# Generate condition command
|
381
|
+
if invert_then:
|
382
|
+
lines.append(f"execute unless {condition} run function {self.current_namespace}:{if_function_name}")
|
383
|
+
else:
|
384
|
+
lines.append(f"execute if {condition} run function {self.current_namespace}:{if_function_name}")
|
385
|
+
|
386
|
+
# Generate the if body function content
|
387
|
+
if_body_lines = [f"# Function: {self.current_namespace}:{if_function_name}"]
|
356
388
|
for stmt in if_stmt.then_body:
|
357
389
|
if isinstance(stmt, VariableAssignment):
|
358
|
-
# Include variable assignments directly
|
359
390
|
cmd = self._variable_assignment_to_command(stmt)
|
360
|
-
|
391
|
+
if_body_lines.append(cmd)
|
361
392
|
elif isinstance(stmt, SayCommand):
|
362
|
-
# Include say commands directly
|
363
393
|
cmd = self._say_command_to_command(stmt)
|
364
|
-
|
394
|
+
if_body_lines.append(cmd)
|
365
395
|
elif isinstance(stmt, RawBlock):
|
366
|
-
|
367
|
-
lines.append(stmt.content)
|
396
|
+
if_body_lines.append(stmt.content)
|
368
397
|
elif isinstance(stmt, IfStatement):
|
369
|
-
# Recursively handle nested if statements
|
370
398
|
cmd = self._if_statement_to_command(stmt)
|
371
|
-
|
399
|
+
if_body_lines.append(cmd)
|
372
400
|
elif isinstance(stmt, WhileLoop):
|
373
|
-
# Handle while loops
|
374
401
|
cmd = self._while_loop_to_command(stmt)
|
375
|
-
|
402
|
+
if_body_lines.append(cmd)
|
376
403
|
elif isinstance(stmt, FunctionCall):
|
377
|
-
# Handle function calls
|
378
404
|
cmd = self._function_call_to_command(stmt)
|
379
|
-
|
405
|
+
if_body_lines.append(cmd)
|
380
406
|
|
381
407
|
# Handle else body if it exists
|
382
408
|
if if_stmt.else_body:
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
if
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
lines.append(
|
409
|
+
if isinstance(if_stmt.else_body, list) and len(if_stmt.else_body) == 1 and isinstance(if_stmt.else_body[0], IfStatement):
|
410
|
+
# Else-if: create an else function wrapper that contains the nested if
|
411
|
+
else_function_name = self._generate_else_function_name()
|
412
|
+
if invert_then:
|
413
|
+
lines.append(f"execute if {condition} run function {self.current_namespace}:{else_function_name}")
|
414
|
+
else:
|
415
|
+
lines.append(f"execute unless {condition} run function {self.current_namespace}:{else_function_name}")
|
416
|
+
else_body_lines = [f"# Function: {self.current_namespace}:{else_function_name}"]
|
417
|
+
nested_cmd = self._if_statement_to_command(if_stmt.else_body[0])
|
418
|
+
for nested_line in nested_cmd.split('\n'):
|
419
|
+
if nested_line:
|
420
|
+
else_body_lines.append(nested_line)
|
421
|
+
self._store_generated_function(else_function_name, else_body_lines)
|
422
|
+
else:
|
423
|
+
# Regular else: compile its body into its own function
|
424
|
+
else_function_name = self._generate_else_function_name()
|
425
|
+
if invert_then:
|
426
|
+
lines.append(f"execute if {condition} run function {self.current_namespace}:{else_function_name}")
|
427
|
+
else:
|
428
|
+
lines.append(f"execute unless {condition} run function {self.current_namespace}:{else_function_name}")
|
429
|
+
else_body_lines = [f"# Function: {self.current_namespace}:{else_function_name}"]
|
430
|
+
for stmt in if_stmt.else_body:
|
431
|
+
if isinstance(stmt, VariableAssignment):
|
432
|
+
cmd = self._variable_assignment_to_command(stmt)
|
433
|
+
else_body_lines.append(cmd)
|
434
|
+
elif isinstance(stmt, SayCommand):
|
435
|
+
cmd = self._say_command_to_command(stmt)
|
436
|
+
else_body_lines.append(cmd)
|
437
|
+
elif isinstance(stmt, RawBlock):
|
438
|
+
else_body_lines.append(stmt.content)
|
439
|
+
elif isinstance(stmt, IfStatement):
|
440
|
+
cmd = self._if_statement_to_command(stmt)
|
441
|
+
else_body_lines.append(cmd)
|
442
|
+
elif isinstance(stmt, WhileLoop):
|
443
|
+
cmd = self._while_loop_to_command(stmt)
|
444
|
+
else_body_lines.append(cmd)
|
445
|
+
elif isinstance(stmt, FunctionCall):
|
446
|
+
cmd = self._function_call_to_command(stmt)
|
447
|
+
else_body_lines.append(cmd)
|
448
|
+
self._store_generated_function(else_function_name, else_body_lines)
|
449
|
+
|
450
|
+
# Store the if function as its own file
|
451
|
+
self._store_generated_function(if_function_name, if_body_lines)
|
403
452
|
|
404
453
|
return "\n".join(lines)
|
405
454
|
|
406
455
|
def _while_loop_to_command(self, while_loop: WhileLoop) -> str:
|
407
|
-
"""Convert while loop to
|
456
|
+
"""Convert while loop to proper Minecraft loop logic."""
|
408
457
|
condition = self._expression_to_condition(while_loop.condition)
|
409
|
-
lines = [
|
458
|
+
lines = []
|
459
|
+
|
460
|
+
# Generate the while loop using a recursive function approach
|
461
|
+
loop_function_name = self._generate_while_function_name()
|
462
|
+
|
463
|
+
# First, call the loop function
|
464
|
+
lines.append(f"function {self.current_namespace}:{loop_function_name}")
|
410
465
|
|
411
|
-
#
|
466
|
+
# Generate the loop function body
|
467
|
+
loop_body_lines = [f"# Function: {self.current_namespace}:{loop_function_name}"]
|
468
|
+
|
469
|
+
# Add the loop body statements
|
412
470
|
for stmt in while_loop.body:
|
413
471
|
if isinstance(stmt, VariableAssignment):
|
414
|
-
# Include variable assignments directly
|
415
472
|
cmd = self._variable_assignment_to_command(stmt)
|
416
|
-
|
473
|
+
loop_body_lines.append(cmd)
|
417
474
|
elif isinstance(stmt, SayCommand):
|
418
|
-
# Include say commands directly
|
419
475
|
cmd = self._say_command_to_command(stmt)
|
420
|
-
|
476
|
+
loop_body_lines.append(cmd)
|
421
477
|
elif isinstance(stmt, RawBlock):
|
422
|
-
|
423
|
-
lines.append(stmt.content)
|
478
|
+
loop_body_lines.append(stmt.content)
|
424
479
|
elif isinstance(stmt, IfStatement):
|
425
|
-
# Recursively handle nested if statements
|
426
480
|
cmd = self._if_statement_to_command(stmt)
|
427
|
-
|
481
|
+
loop_body_lines.append(cmd)
|
428
482
|
elif isinstance(stmt, WhileLoop):
|
429
|
-
# Handle nested while loops
|
430
483
|
cmd = self._while_loop_to_command(stmt)
|
431
|
-
|
484
|
+
loop_body_lines.append(cmd)
|
432
485
|
elif isinstance(stmt, FunctionCall):
|
433
|
-
# Handle function calls
|
434
486
|
cmd = self._function_call_to_command(stmt)
|
435
|
-
|
487
|
+
loop_body_lines.append(cmd)
|
488
|
+
|
489
|
+
# Add the recursive call at the end to continue the loop
|
490
|
+
if self._is_scoreboard_condition(while_loop.condition):
|
491
|
+
loop_body_lines.append(f"execute if score {condition} run function {self.current_namespace}:{loop_function_name}")
|
492
|
+
else:
|
493
|
+
loop_body_lines.append(f"execute if {condition} run function {self.current_namespace}:{loop_function_name}")
|
494
|
+
|
495
|
+
# Store the loop function as its own file
|
496
|
+
self._store_generated_function(loop_function_name, loop_body_lines)
|
436
497
|
|
437
498
|
return "\n".join(lines)
|
438
499
|
|
500
|
+
def _is_scoreboard_condition(self, expression: Any) -> bool:
|
501
|
+
"""Check if an expression is a scoreboard comparison."""
|
502
|
+
if isinstance(expression, BinaryExpression):
|
503
|
+
# Check if it's comparing a scoreboard value
|
504
|
+
if isinstance(expression.left, VariableSubstitution) or isinstance(expression.right, VariableSubstitution):
|
505
|
+
return True
|
506
|
+
return False
|
507
|
+
|
508
|
+
def _generate_if_function_name(self) -> str:
|
509
|
+
"""Generate a unique name for an if function."""
|
510
|
+
if not hasattr(self, 'if_counter'):
|
511
|
+
self.if_counter = 0
|
512
|
+
self.if_counter += 1
|
513
|
+
return f"if_{self.if_counter}"
|
514
|
+
|
515
|
+
def _generate_else_function_name(self) -> str:
|
516
|
+
"""Generate a unique name for an else function."""
|
517
|
+
if not hasattr(self, 'else_counter'):
|
518
|
+
self.else_counter = 0
|
519
|
+
self.else_counter += 1
|
520
|
+
return f"else_{self.else_counter}"
|
521
|
+
|
522
|
+
def _generate_while_function_name(self) -> str:
|
523
|
+
"""Generate a unique name for a while function."""
|
524
|
+
if not hasattr(self, 'while_counter'):
|
525
|
+
self.while_counter = 0
|
526
|
+
self.while_counter += 1
|
527
|
+
return f"while_{self.while_counter}"
|
528
|
+
|
529
|
+
def _store_generated_function(self, name: str, lines: List[str]):
|
530
|
+
"""Store a generated function as a separate file under the same namespace."""
|
531
|
+
if self.dir_map:
|
532
|
+
functions_dir = self.output_dir / "data" / self.current_namespace / self.dir_map.function
|
533
|
+
else:
|
534
|
+
functions_dir = self.output_dir / "data" / self.current_namespace / "functions"
|
535
|
+
functions_dir.mkdir(parents=True, exist_ok=True)
|
536
|
+
func_file = functions_dir / f"{name}.mcfunction"
|
537
|
+
with open(func_file, 'w') as f:
|
538
|
+
f.write("\n".join(lines) + "\n")
|
539
|
+
|
439
540
|
def _function_call_to_command(self, func_call: FunctionCall) -> str:
|
440
541
|
"""Convert function call to execute command."""
|
441
542
|
if func_call.scope:
|
@@ -446,25 +547,188 @@ class MDLCompiler:
|
|
446
547
|
def _expression_to_value(self, expression: Any) -> str:
|
447
548
|
"""Convert expression to a value string."""
|
448
549
|
if isinstance(expression, LiteralExpression):
|
550
|
+
# Format numbers as integers if possible
|
551
|
+
if isinstance(expression.value, (int, float)):
|
552
|
+
try:
|
553
|
+
v = float(expression.value)
|
554
|
+
if v.is_integer():
|
555
|
+
return str(int(v))
|
556
|
+
return str(v)
|
557
|
+
except Exception:
|
558
|
+
return str(expression.value)
|
449
559
|
return str(expression.value)
|
450
560
|
elif isinstance(expression, VariableSubstitution):
|
451
561
|
objective = self.variables.get(expression.name, expression.name)
|
452
562
|
scope = expression.scope.strip("<>")
|
453
563
|
return f"score {scope} {objective}"
|
454
564
|
elif isinstance(expression, BinaryExpression):
|
455
|
-
|
456
|
-
|
457
|
-
|
565
|
+
# For complex expressions, we need to use temporary variables
|
566
|
+
temp_var = self._generate_temp_variable_name()
|
567
|
+
self._compile_expression_to_temp(expression, temp_var)
|
568
|
+
return f"score @s {temp_var}"
|
458
569
|
elif isinstance(expression, ParenthesizedExpression):
|
459
|
-
return
|
570
|
+
return self._expression_to_value(expression.expression)
|
460
571
|
else:
|
461
572
|
return str(expression)
|
462
573
|
|
463
574
|
def _expression_to_condition(self, expression: Any) -> str:
|
464
|
-
"""Convert expression to a condition string."""
|
575
|
+
"""Legacy: Convert expression to a naive condition string (internal use)."""
|
465
576
|
if isinstance(expression, BinaryExpression):
|
466
577
|
left = self._expression_to_value(expression.left)
|
467
578
|
right = self._expression_to_value(expression.right)
|
468
579
|
return f"{left} {expression.operator} {right}"
|
469
580
|
else:
|
470
581
|
return self._expression_to_value(expression)
|
582
|
+
|
583
|
+
def _build_condition(self, expression: Any) -> (str, bool):
|
584
|
+
"""Build a valid Minecraft execute condition.
|
585
|
+
Returns (condition_string, invert_then) where invert_then True means the THEN branch should use 'unless'.
|
586
|
+
"""
|
587
|
+
# Default: generic expression string, no inversion
|
588
|
+
invert_then = False
|
589
|
+
|
590
|
+
if isinstance(expression, BinaryExpression):
|
591
|
+
left = expression.left
|
592
|
+
right = expression.right
|
593
|
+
op = expression.operator
|
594
|
+
# Variable vs literal
|
595
|
+
if isinstance(left, VariableSubstitution) and isinstance(right, LiteralExpression) and isinstance(right.value, (int, float)):
|
596
|
+
objective = self.variables.get(left.name, left.name)
|
597
|
+
scope = left.scope.strip("<>")
|
598
|
+
# Normalize number
|
599
|
+
try:
|
600
|
+
v = float(right.value)
|
601
|
+
except Exception:
|
602
|
+
v = None
|
603
|
+
if v is not None:
|
604
|
+
n = int(v) if float(v).is_integer() else v
|
605
|
+
if op == TokenType.GREATER:
|
606
|
+
rng = f"{int(n)+1}.." if isinstance(n, int) else f"{v+1}.."
|
607
|
+
return (f"score {scope} {objective} matches {rng}", False)
|
608
|
+
if op == TokenType.GREATER_EQUAL:
|
609
|
+
rng = f"{int(n)}.."
|
610
|
+
return (f"score {scope} {objective} matches {rng}", False)
|
611
|
+
if op == TokenType.LESS:
|
612
|
+
rng = f"..{int(n)-1}"
|
613
|
+
return (f"score {scope} {objective} matches {rng}", False)
|
614
|
+
if op == TokenType.LESS_EQUAL:
|
615
|
+
rng = f"..{int(n)}"
|
616
|
+
return (f"score {scope} {objective} matches {rng}", False)
|
617
|
+
if op == TokenType.EQUAL:
|
618
|
+
rng = f"{int(n)}"
|
619
|
+
return (f"score {scope} {objective} matches {rng}", False)
|
620
|
+
if op == TokenType.NOT_EQUAL:
|
621
|
+
rng = f"{int(n)}"
|
622
|
+
return (f"score {scope} {objective} matches {rng}", True)
|
623
|
+
# Variable vs variable
|
624
|
+
if isinstance(left, VariableSubstitution) and isinstance(right, VariableSubstitution):
|
625
|
+
lobj = self.variables.get(left.name, left.name)
|
626
|
+
lscope = left.scope.strip("<>")
|
627
|
+
robj = self.variables.get(right.name, right.name)
|
628
|
+
rscope = right.scope.strip("<>")
|
629
|
+
if op in (TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.LESS, TokenType.LESS_EQUAL, TokenType.EQUAL):
|
630
|
+
comp_map = {
|
631
|
+
TokenType.GREATER: ">",
|
632
|
+
TokenType.GREATER_EQUAL: ">=",
|
633
|
+
TokenType.LESS: "<",
|
634
|
+
TokenType.LESS_EQUAL: "<=",
|
635
|
+
TokenType.EQUAL: "="
|
636
|
+
}
|
637
|
+
comp = comp_map[op]
|
638
|
+
return (f"score {lscope} {lobj} {comp} {rscope} {robj}", False)
|
639
|
+
if op == TokenType.NOT_EQUAL:
|
640
|
+
# Use equals with inversion
|
641
|
+
return (f"score {lscope} {lobj} = {rscope} {robj}", True)
|
642
|
+
|
643
|
+
# Fallback: treat as generic condition string
|
644
|
+
return (self._expression_to_condition(expression), False)
|
645
|
+
|
646
|
+
def _compile_expression_to_temp(self, expression: BinaryExpression, temp_var: str):
|
647
|
+
"""Compile a complex expression to a temporary variable using valid Minecraft commands."""
|
648
|
+
left_temp = None
|
649
|
+
right_temp = None
|
650
|
+
|
651
|
+
if isinstance(expression.left, BinaryExpression):
|
652
|
+
# Left side is complex - compile it first
|
653
|
+
left_temp = self._generate_temp_variable_name()
|
654
|
+
self._compile_expression_to_temp(expression.left, left_temp)
|
655
|
+
left_value = f"score @s {left_temp}"
|
656
|
+
else:
|
657
|
+
left_value = self._expression_to_value(expression.left)
|
658
|
+
|
659
|
+
if isinstance(expression.right, BinaryExpression):
|
660
|
+
# Right side is complex - compile it first
|
661
|
+
right_temp = self._generate_temp_variable_name()
|
662
|
+
self._compile_expression_to_temp(expression.right, right_temp)
|
663
|
+
right_value = f"score @s {right_temp}"
|
664
|
+
else:
|
665
|
+
right_value = self._expression_to_value(expression.right)
|
666
|
+
|
667
|
+
# Generate the operation command
|
668
|
+
if expression.operator == "PLUS":
|
669
|
+
if isinstance(expression.left, BinaryExpression):
|
670
|
+
self._store_temp_command(f"scoreboard players operation @s {temp_var} = @s {left_temp}")
|
671
|
+
else:
|
672
|
+
self._store_temp_command(f"scoreboard players set @s {temp_var} {left_value}")
|
673
|
+
|
674
|
+
if isinstance(expression.right, BinaryExpression):
|
675
|
+
self._store_temp_command(f"scoreboard players add @s {temp_var} {right_value}")
|
676
|
+
else:
|
677
|
+
self._store_temp_command(f"scoreboard players add @s {temp_var} {right_value}")
|
678
|
+
|
679
|
+
elif expression.operator == "MINUS":
|
680
|
+
if isinstance(expression.left, BinaryExpression):
|
681
|
+
self._store_temp_command(f"scoreboard players operation @s {temp_var} = @s {left_temp}")
|
682
|
+
else:
|
683
|
+
self._store_temp_command(f"scoreboard players set @s {temp_var} {left_value}")
|
684
|
+
|
685
|
+
if isinstance(expression.right, BinaryExpression):
|
686
|
+
self._store_temp_command(f"scoreboard players remove @s {temp_var} {right_value}")
|
687
|
+
else:
|
688
|
+
self._store_temp_command(f"scoreboard players remove @s {temp_var} {right_value}")
|
689
|
+
|
690
|
+
elif expression.operator == "MULTIPLY":
|
691
|
+
if isinstance(expression.left, BinaryExpression):
|
692
|
+
self._store_temp_command(f"scoreboard players operation @s {temp_var} = @s {left_temp}")
|
693
|
+
else:
|
694
|
+
self._store_temp_command(f"scoreboard players set @s {temp_var} {left_value}")
|
695
|
+
|
696
|
+
if isinstance(expression.right, BinaryExpression):
|
697
|
+
self._store_temp_command(f"scoreboard players operation @s {temp_var} *= @s {right_temp}")
|
698
|
+
else:
|
699
|
+
# For literal values, we need to use a different approach
|
700
|
+
if isinstance(expression.right, LiteralExpression):
|
701
|
+
self._store_temp_command(f"scoreboard players multiply @s {temp_var} {expression.right.value}")
|
702
|
+
else:
|
703
|
+
self._store_temp_command(f"scoreboard players operation @s {temp_var} *= {right_value}")
|
704
|
+
|
705
|
+
elif expression.operator == "DIVIDE":
|
706
|
+
if isinstance(expression.left, BinaryExpression):
|
707
|
+
self._store_temp_command(f"scoreboard players operation @s {temp_var} = @s {left_temp}")
|
708
|
+
else:
|
709
|
+
self._store_temp_command(f"scoreboard players set @s {temp_var} {left_value}")
|
710
|
+
|
711
|
+
if isinstance(expression.right, BinaryExpression):
|
712
|
+
self._store_temp_command(f"scoreboard players operation @s {temp_var} /= @s {right_temp}")
|
713
|
+
else:
|
714
|
+
# For literal values, we need to use a different approach
|
715
|
+
if isinstance(expression.right, LiteralExpression):
|
716
|
+
self._store_temp_command(f"scoreboard players divide @s {temp_var} {expression.right.value}")
|
717
|
+
else:
|
718
|
+
self._store_temp_command(f"scoreboard players operation @s {temp_var} /= {right_value}")
|
719
|
+
else:
|
720
|
+
# For other operators, just set the value
|
721
|
+
self._store_temp_command(f"scoreboard players set @s {temp_var} 0")
|
722
|
+
|
723
|
+
def _store_temp_command(self, command: str):
|
724
|
+
"""Store a temporary command for later execution."""
|
725
|
+
if not hasattr(self, 'temp_commands'):
|
726
|
+
self.temp_commands = []
|
727
|
+
self.temp_commands.append(command)
|
728
|
+
|
729
|
+
def _generate_temp_variable_name(self) -> str:
|
730
|
+
"""Generate a unique temporary variable name."""
|
731
|
+
if not hasattr(self, 'temp_counter'):
|
732
|
+
self.temp_counter = 0
|
733
|
+
self.temp_counter += 1
|
734
|
+
return f"temp_{self.temp_counter}"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: minecraft-datapack-language
|
3
|
-
Version: 15.4.
|
3
|
+
Version: 15.4.33
|
4
4
|
Summary: Compile MDL language with explicit scoping into a Minecraft datapack (1.21+ ready). Features variables, control flow, error handling, and VS Code extension.
|
5
5
|
Project-URL: Homepage, https://www.mcmdl.com
|
6
6
|
Project-URL: Documentation, https://www.mcmdl.com/docs
|
@@ -28,7 +28,6 @@ A **modern, scope-aware language** that lets you write Minecraft datapacks with
|
|
28
28
|
🔧 **[VS Code Extension](https://marketplace.visualstudio.com/items?itemName=mdl.minecraft-datapack-language)** - Syntax highlighting, IntelliSense, and snippets
|
29
29
|
|
30
30
|

|
31
|
-

|
32
31
|

|
33
32
|

|
34
33
|

|
@@ -1,17 +1,17 @@
|
|
1
1
|
minecraft_datapack_language/__init__.py,sha256=YoTmZWZVH6POAVYMvOTEBKdC-cxQsWi2VomSWZDgYFw,1158
|
2
|
-
minecraft_datapack_language/_version.py,sha256=
|
2
|
+
minecraft_datapack_language/_version.py,sha256=oHNM_9N4Gw1WIjw70v38mQJDgIkD6Qbx1TSXwhIvfNI,708
|
3
3
|
minecraft_datapack_language/ast_nodes.py,sha256=nbWrRz137MGMRpmnq8QkXNzrtlaCgyPEknytbkrS_M8,3899
|
4
|
-
minecraft_datapack_language/cli.py,sha256
|
4
|
+
minecraft_datapack_language/cli.py,sha256=-TMAL1HCCtwf0aG46x88MVBsYUswPRCVhy854li7X9c,9780
|
5
5
|
minecraft_datapack_language/dir_map.py,sha256=HmxFkuvWGkzHF8o_GFb4BpuMCRc6QMw8UbmcAI8JVdY,1788
|
6
|
-
minecraft_datapack_language/mdl_compiler.py,sha256=
|
6
|
+
minecraft_datapack_language/mdl_compiler.py,sha256=nS_D2f_G8lcDxUOiADNJWTJQwTnXoQ-W4amyhw6xJoY,34673
|
7
7
|
minecraft_datapack_language/mdl_errors.py,sha256=r0Gu3KhoX1YLPAVW_iO7Q_fPgaf_Dv9tOGSOdKNSzmw,16114
|
8
8
|
minecraft_datapack_language/mdl_lexer.py,sha256=CjbEUpuuF4eU_ucA_WIhw6wSMcHGk2BchtQ0bLAGvwg,22033
|
9
9
|
minecraft_datapack_language/mdl_linter.py,sha256=z85xoAglENurCh30bR7kEHZ_JeMxcYaLDcGNRAl-RAI,17253
|
10
10
|
minecraft_datapack_language/mdl_parser.py,sha256=aQPKcmATM9BOMzO7vCXmMdxU1qjOJNLCSAKJopu5h3g,23429
|
11
11
|
minecraft_datapack_language/utils.py,sha256=Aq0HAGlXqj9BUTEjaEilpvzEW0EtZYYMMwOqG9db6dE,684
|
12
|
-
minecraft_datapack_language-15.4.
|
13
|
-
minecraft_datapack_language-15.4.
|
14
|
-
minecraft_datapack_language-15.4.
|
15
|
-
minecraft_datapack_language-15.4.
|
16
|
-
minecraft_datapack_language-15.4.
|
17
|
-
minecraft_datapack_language-15.4.
|
12
|
+
minecraft_datapack_language-15.4.33.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
13
|
+
minecraft_datapack_language-15.4.33.dist-info/METADATA,sha256=-vx5gJu0ZmkHmVVnlBIaa4rtBEhhfoi32BgXl1NnAqg,8360
|
14
|
+
minecraft_datapack_language-15.4.33.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
15
|
+
minecraft_datapack_language-15.4.33.dist-info/entry_points.txt,sha256=c6vjBeCiyQflvPHBRyBk2nJCSfYt3Oc7Sc9V87ySi_U,108
|
16
|
+
minecraft_datapack_language-15.4.33.dist-info/top_level.txt,sha256=ADtFI476tbKLLxEAA-aJQAfg53MA3k_DOb0KTFiggfw,28
|
17
|
+
minecraft_datapack_language-15.4.33.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|