minecraft-datapack-language 15.4.27__py3-none-any.whl → 15.4.29__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/__init__.py +17 -2
- minecraft_datapack_language/_version.py +2 -2
- minecraft_datapack_language/ast_nodes.py +87 -59
- minecraft_datapack_language/mdl_compiler.py +470 -0
- minecraft_datapack_language/mdl_errors.py +14 -0
- minecraft_datapack_language/mdl_lexer.py +624 -0
- minecraft_datapack_language/mdl_parser.py +573 -0
- minecraft_datapack_language-15.4.29.dist-info/METADATA +266 -0
- minecraft_datapack_language-15.4.29.dist-info/RECORD +16 -0
- minecraft_datapack_language/cli.py +0 -159
- minecraft_datapack_language/cli_build.py +0 -1292
- minecraft_datapack_language/cli_check.py +0 -155
- minecraft_datapack_language/cli_colors.py +0 -264
- minecraft_datapack_language/cli_help.py +0 -508
- minecraft_datapack_language/cli_new.py +0 -300
- minecraft_datapack_language/cli_utils.py +0 -276
- minecraft_datapack_language/expression_processor.py +0 -352
- minecraft_datapack_language/linter.py +0 -409
- minecraft_datapack_language/mdl_lexer_js.py +0 -754
- minecraft_datapack_language/mdl_parser_js.py +0 -1049
- minecraft_datapack_language/pack.py +0 -758
- minecraft_datapack_language-15.4.27.dist-info/METADATA +0 -1274
- minecraft_datapack_language-15.4.27.dist-info/RECORD +0 -25
- {minecraft_datapack_language-15.4.27.dist-info → minecraft_datapack_language-15.4.29.dist-info}/WHEEL +0 -0
- {minecraft_datapack_language-15.4.27.dist-info → minecraft_datapack_language-15.4.29.dist-info}/entry_points.txt +0 -0
- {minecraft_datapack_language-15.4.27.dist-info → minecraft_datapack_language-15.4.29.dist-info}/licenses/LICENSE +0 -0
- {minecraft_datapack_language-15.4.27.dist-info → minecraft_datapack_language-15.4.29.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,470 @@
|
|
1
|
+
"""
|
2
|
+
MDL Compiler - Converts MDL AST into complete Minecraft datapack
|
3
|
+
Simplified version that focuses on generating actual statements for testing
|
4
|
+
"""
|
5
|
+
|
6
|
+
import os
|
7
|
+
import json
|
8
|
+
import shutil
|
9
|
+
from pathlib import Path
|
10
|
+
from typing import Dict, List, Any, Optional
|
11
|
+
from .ast_nodes import (
|
12
|
+
Program, PackDeclaration, NamespaceDeclaration, TagDeclaration,
|
13
|
+
VariableDeclaration, VariableAssignment, VariableSubstitution, FunctionDeclaration,
|
14
|
+
FunctionCall, IfStatement, WhileLoop, HookDeclaration, RawBlock,
|
15
|
+
SayCommand, BinaryExpression, LiteralExpression, ParenthesizedExpression
|
16
|
+
)
|
17
|
+
from .dir_map import get_dir_map, DirMap
|
18
|
+
from .mdl_errors import MDLCompilerError
|
19
|
+
|
20
|
+
|
21
|
+
class MDLCompiler:
|
22
|
+
"""
|
23
|
+
Simplified compiler for the MDL language that generates actual statements.
|
24
|
+
"""
|
25
|
+
|
26
|
+
def __init__(self, output_dir: str = "dist"):
|
27
|
+
self.output_dir = Path(output_dir)
|
28
|
+
self.dir_map: Optional[DirMap] = None
|
29
|
+
self.current_namespace = "mdl"
|
30
|
+
self.variables: Dict[str, str] = {} # name -> objective mapping
|
31
|
+
|
32
|
+
def compile(self, ast: Program, source_dir: str = None) -> str:
|
33
|
+
"""Compile MDL AST into a complete Minecraft datapack."""
|
34
|
+
try:
|
35
|
+
# Use source_dir as output directory if provided
|
36
|
+
if source_dir:
|
37
|
+
output_dir = Path(source_dir)
|
38
|
+
else:
|
39
|
+
output_dir = self.output_dir
|
40
|
+
|
41
|
+
# Clean output directory
|
42
|
+
if output_dir.exists():
|
43
|
+
shutil.rmtree(output_dir)
|
44
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
45
|
+
|
46
|
+
# Temporarily set output directory
|
47
|
+
original_output_dir = self.output_dir
|
48
|
+
self.output_dir = output_dir
|
49
|
+
|
50
|
+
# Set up directory mapping based on pack format
|
51
|
+
pack_format = ast.pack.pack_format if ast.pack else 15
|
52
|
+
self.dir_map = get_dir_map(pack_format)
|
53
|
+
|
54
|
+
# Create pack.mcmeta
|
55
|
+
self._create_pack_mcmeta(ast.pack)
|
56
|
+
|
57
|
+
# Create data directory structure
|
58
|
+
data_dir = self.output_dir / "data"
|
59
|
+
data_dir.mkdir(parents=True, exist_ok=True)
|
60
|
+
|
61
|
+
# Set namespace
|
62
|
+
if ast.namespace:
|
63
|
+
self.current_namespace = ast.namespace.name
|
64
|
+
|
65
|
+
# Create namespace directory
|
66
|
+
namespace_dir = data_dir / self.current_namespace
|
67
|
+
namespace_dir.mkdir(parents=True, exist_ok=True)
|
68
|
+
|
69
|
+
# Compile all components
|
70
|
+
self._compile_variables(ast.variables, namespace_dir)
|
71
|
+
self._compile_functions(ast.functions, namespace_dir)
|
72
|
+
self._compile_hooks(ast.hooks, namespace_dir)
|
73
|
+
self._compile_statements(ast.statements, namespace_dir)
|
74
|
+
self._compile_tags(ast.tags, source_dir)
|
75
|
+
|
76
|
+
# Create load and tick functions for hooks
|
77
|
+
self._create_hook_functions(ast.hooks, namespace_dir)
|
78
|
+
|
79
|
+
# Return the output directory path
|
80
|
+
result = str(self.output_dir)
|
81
|
+
|
82
|
+
# Restore original output directory
|
83
|
+
self.output_dir = original_output_dir
|
84
|
+
|
85
|
+
return result
|
86
|
+
|
87
|
+
except Exception as e:
|
88
|
+
# Restore original output directory on error
|
89
|
+
if 'original_output_dir' in locals():
|
90
|
+
self.output_dir = original_output_dir
|
91
|
+
|
92
|
+
if isinstance(e, MDLCompilerError):
|
93
|
+
raise e
|
94
|
+
else:
|
95
|
+
raise MDLCompilerError(f"Compilation failed: {str(e)}", "Check the AST structure")
|
96
|
+
|
97
|
+
def _create_pack_mcmeta(self, pack: Optional[PackDeclaration]):
|
98
|
+
"""Create pack.mcmeta file."""
|
99
|
+
if not pack:
|
100
|
+
pack_data = {
|
101
|
+
"pack": {
|
102
|
+
"pack_format": 15,
|
103
|
+
"description": "MDL Generated Datapack"
|
104
|
+
}
|
105
|
+
}
|
106
|
+
else:
|
107
|
+
pack_data = {
|
108
|
+
"pack": {
|
109
|
+
"pack_format": pack.pack_format,
|
110
|
+
"description": pack.description
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
pack_mcmeta_path = self.output_dir / "pack.mcmeta"
|
115
|
+
with open(pack_mcmeta_path, 'w') as f:
|
116
|
+
json.dump(pack_data, f, indent=2)
|
117
|
+
|
118
|
+
def _compile_variables(self, variables: List[VariableDeclaration], namespace_dir: Path):
|
119
|
+
"""Compile variable declarations into scoreboard objectives."""
|
120
|
+
for var in variables:
|
121
|
+
objective_name = var.name
|
122
|
+
self.variables[var.name] = objective_name
|
123
|
+
print(f"Variable: {var.name} -> scoreboard objective '{objective_name}'")
|
124
|
+
|
125
|
+
def _compile_functions(self, functions: List[FunctionDeclaration], namespace_dir: Path):
|
126
|
+
"""Compile function declarations into .mcfunction files."""
|
127
|
+
if self.dir_map:
|
128
|
+
functions_dir = namespace_dir / self.dir_map.function
|
129
|
+
else:
|
130
|
+
functions_dir = namespace_dir / "functions"
|
131
|
+
functions_dir.mkdir(parents=True, exist_ok=True)
|
132
|
+
|
133
|
+
for func in functions:
|
134
|
+
func_file = functions_dir / f"{func.name}.mcfunction"
|
135
|
+
content = self._generate_function_content(func)
|
136
|
+
|
137
|
+
with open(func_file, 'w') as f:
|
138
|
+
f.write(content)
|
139
|
+
|
140
|
+
print(f"Function: {func.namespace}:{func.name} -> {func_file}")
|
141
|
+
|
142
|
+
def _generate_function_content(self, func: FunctionDeclaration) -> str:
|
143
|
+
"""Generate the content of a .mcfunction file."""
|
144
|
+
lines = []
|
145
|
+
lines.append(f"# Function: {func.namespace}:{func.name}")
|
146
|
+
if func.scope:
|
147
|
+
lines.append(f"# Scope: {func.scope}")
|
148
|
+
lines.append("")
|
149
|
+
|
150
|
+
# Generate commands from function body
|
151
|
+
for statement in func.body:
|
152
|
+
cmd = self._statement_to_command(statement)
|
153
|
+
if cmd:
|
154
|
+
lines.append(cmd)
|
155
|
+
|
156
|
+
return "\n".join(lines)
|
157
|
+
|
158
|
+
def _compile_hooks(self, hooks: List[HookDeclaration], namespace_dir: Path):
|
159
|
+
"""Compile hook declarations."""
|
160
|
+
for hook in hooks:
|
161
|
+
print(f"Hook: {hook.hook_type} -> {hook.namespace}:{hook.name}")
|
162
|
+
|
163
|
+
def _compile_statements(self, statements: List[Any], namespace_dir: Path):
|
164
|
+
"""Compile top-level statements."""
|
165
|
+
for statement in statements:
|
166
|
+
if isinstance(statement, FunctionCall):
|
167
|
+
print(f"Top-level exec: {statement.namespace}:{statement.name}")
|
168
|
+
elif isinstance(statement, RawBlock):
|
169
|
+
print(f"Top-level raw block: {len(statement.content)} characters")
|
170
|
+
|
171
|
+
def _compile_tags(self, tags: List[TagDeclaration], source_dir: str):
|
172
|
+
"""Compile tag declarations and copy referenced JSON files."""
|
173
|
+
source_path = Path(source_dir) if source_dir else None
|
174
|
+
|
175
|
+
for tag in tags:
|
176
|
+
if tag.tag_type == "recipe":
|
177
|
+
tag_dir = self.output_dir / "data" / "minecraft" / self.dir_map.tags_item
|
178
|
+
elif tag.tag_type == "loot_table":
|
179
|
+
tag_dir = self.output_dir / "data" / "minecraft" / self.dir_map.tags_item
|
180
|
+
elif tag.tag_type == "advancement":
|
181
|
+
tag_dir = self.output_dir / "data" / "minecraft" / self.dir_map.tags_item
|
182
|
+
elif tag.tag_type == "item_modifier":
|
183
|
+
tag_dir = self.output_dir / "data" / "minecraft" / self.dir_map.tags_item
|
184
|
+
elif tag.tag_type == "predicate":
|
185
|
+
tag_dir = self.output_dir / "data" / "minecraft" / self.dir_map.tags_item
|
186
|
+
elif tag.tag_type == "structure":
|
187
|
+
tag_dir = self.output_dir / "data" / "minecraft" / self.dir_map.tags_item
|
188
|
+
else:
|
189
|
+
continue
|
190
|
+
|
191
|
+
tag_dir.mkdir(parents=True, exist_ok=True)
|
192
|
+
tag_file = tag_dir / f"{tag.name}.json"
|
193
|
+
|
194
|
+
if source_path:
|
195
|
+
source_json = source_path / tag.file_path
|
196
|
+
if source_json.exists():
|
197
|
+
shutil.copy2(source_json, tag_file)
|
198
|
+
print(f"Tag {tag.tag_type}: {tag.name} -> {tag_file}")
|
199
|
+
else:
|
200
|
+
tag_data = {"values": [f"{self.current_namespace}:{tag.name}"]}
|
201
|
+
with open(tag_file, 'w') as f:
|
202
|
+
json.dump(tag_data, f, indent=2)
|
203
|
+
print(f"Tag {tag.tag_type}: {tag.name} -> {tag_file} (placeholder)")
|
204
|
+
else:
|
205
|
+
tag_data = {"values": [f"{self.current_namespace}:{tag.name}"]}
|
206
|
+
with open(tag_file, 'w') as f:
|
207
|
+
json.dump(tag_data, f, indent=2)
|
208
|
+
print(f"Tag {tag.tag_type}: {tag.name} -> {tag_file} (placeholder)")
|
209
|
+
|
210
|
+
def _create_hook_functions(self, hooks: List[HookDeclaration], namespace_dir: Path):
|
211
|
+
"""Create load.mcfunction and tick.mcfunction for hooks."""
|
212
|
+
if self.dir_map:
|
213
|
+
functions_dir = namespace_dir / self.dir_map.function
|
214
|
+
else:
|
215
|
+
functions_dir = namespace_dir / "functions"
|
216
|
+
|
217
|
+
# Create load function
|
218
|
+
load_content = self._generate_load_function(hooks)
|
219
|
+
load_file = functions_dir / "load.mcfunction"
|
220
|
+
with open(load_file, 'w') as f:
|
221
|
+
f.write(load_content)
|
222
|
+
|
223
|
+
# Create tick function if needed
|
224
|
+
tick_hooks = [h for h in hooks if h.hook_type == "on_tick"]
|
225
|
+
if tick_hooks:
|
226
|
+
tick_content = self._generate_tick_function(tick_hooks)
|
227
|
+
tick_file = functions_dir / "tick.mcfunction"
|
228
|
+
with open(tick_file, 'w') as f:
|
229
|
+
f.write(tick_content)
|
230
|
+
|
231
|
+
def _generate_load_function(self, hooks: List[HookDeclaration]) -> str:
|
232
|
+
"""Generate the content of load.mcfunction."""
|
233
|
+
lines = [
|
234
|
+
"# Load function - runs when datapack loads",
|
235
|
+
"# Generated by MDL Compiler",
|
236
|
+
""
|
237
|
+
]
|
238
|
+
|
239
|
+
# Add scoreboard objective creation for variables
|
240
|
+
for var_name, objective in self.variables.items():
|
241
|
+
lines.append(f"scoreboard objectives add {objective} dummy \"{var_name}\"")
|
242
|
+
|
243
|
+
lines.append("")
|
244
|
+
|
245
|
+
# Add on_load hook calls
|
246
|
+
load_hooks = [h for h in hooks if h.hook_type == "on_load"]
|
247
|
+
for hook in load_hooks:
|
248
|
+
if hook.scope:
|
249
|
+
lines.append(f"execute as {hook.scope.strip('<>')} run function {hook.namespace}:{hook.name}")
|
250
|
+
else:
|
251
|
+
lines.append(f"function {hook.namespace}:{hook.name}")
|
252
|
+
|
253
|
+
return "\n".join(lines)
|
254
|
+
|
255
|
+
def _generate_tick_function(self, tick_hooks: List[HookDeclaration]) -> str:
|
256
|
+
"""Generate the content of tick.mcfunction."""
|
257
|
+
lines = [
|
258
|
+
"# Tick function - runs every tick",
|
259
|
+
"# Generated by MDL Compiler",
|
260
|
+
""
|
261
|
+
]
|
262
|
+
|
263
|
+
# Add on_tick hook calls
|
264
|
+
for hook in tick_hooks:
|
265
|
+
if hook.scope:
|
266
|
+
scope = hook.scope.strip("<>")
|
267
|
+
lines.append(f"execute as {scope} run function {hook.namespace}:{hook.name}")
|
268
|
+
else:
|
269
|
+
lines.append(f"function {hook.namespace}:{hook.name}")
|
270
|
+
|
271
|
+
return "\n".join(lines)
|
272
|
+
|
273
|
+
def _statement_to_command(self, statement: Any) -> Optional[str]:
|
274
|
+
"""Convert an AST statement to a Minecraft command."""
|
275
|
+
if isinstance(statement, VariableAssignment):
|
276
|
+
return self._variable_assignment_to_command(statement)
|
277
|
+
elif isinstance(statement, SayCommand):
|
278
|
+
return self._say_command_to_command(statement)
|
279
|
+
elif isinstance(statement, RawBlock):
|
280
|
+
return statement.content
|
281
|
+
elif isinstance(statement, IfStatement):
|
282
|
+
return self._if_statement_to_command(statement)
|
283
|
+
elif isinstance(statement, WhileLoop):
|
284
|
+
return self._while_loop_to_command(statement)
|
285
|
+
elif isinstance(statement, FunctionCall):
|
286
|
+
return self._function_call_to_command(statement)
|
287
|
+
else:
|
288
|
+
return None
|
289
|
+
|
290
|
+
def _variable_assignment_to_command(self, assignment: VariableAssignment) -> str:
|
291
|
+
"""Convert variable assignment to scoreboard command."""
|
292
|
+
objective = self.variables.get(assignment.name, assignment.name)
|
293
|
+
value = self._expression_to_value(assignment.value)
|
294
|
+
scope = assignment.scope.strip("<>")
|
295
|
+
return f"scoreboard players set {scope} {objective} {value}"
|
296
|
+
|
297
|
+
def _say_command_to_command(self, say: SayCommand) -> str:
|
298
|
+
"""Convert say command to tellraw command with JSON formatting."""
|
299
|
+
if not say.variables:
|
300
|
+
return f'tellraw @a {{"text":"{say.message}"}}'
|
301
|
+
else:
|
302
|
+
return self._build_tellraw_json(say.message, say.variables)
|
303
|
+
|
304
|
+
def _build_tellraw_json(self, message: str, variables: List[VariableSubstitution]) -> str:
|
305
|
+
"""Build complex tellraw JSON with variable substitutions."""
|
306
|
+
parts = []
|
307
|
+
current_pos = 0
|
308
|
+
|
309
|
+
for var in variables:
|
310
|
+
var_pattern = f"${var.name}{var.scope}$"
|
311
|
+
var_pos = message.find(var_pattern, current_pos)
|
312
|
+
|
313
|
+
if var_pos != -1:
|
314
|
+
if var_pos > current_pos:
|
315
|
+
text_before = message[current_pos:var_pos]
|
316
|
+
parts.append(f'{{"text":"{text_before}"}}')
|
317
|
+
|
318
|
+
objective = self.variables.get(var.name, var.name)
|
319
|
+
scope = var.scope.strip("<>")
|
320
|
+
parts.append(f'{{"score":{{"name":"{scope}","objective":"{objective}"}}}}')
|
321
|
+
|
322
|
+
current_pos = var_pos + len(var_pattern)
|
323
|
+
|
324
|
+
if current_pos < len(message):
|
325
|
+
text_after = message[current_pos:]
|
326
|
+
parts.append(text_after)
|
327
|
+
|
328
|
+
if len(parts) == 1:
|
329
|
+
if isinstance(parts[0], str) and not parts[0].startswith('{"'):
|
330
|
+
return f'tellraw @a {{"text":"{parts[0]}"}}'
|
331
|
+
return f'tellraw @a {parts[0]}'
|
332
|
+
else:
|
333
|
+
first_part = parts[0]
|
334
|
+
remaining_parts = parts[1:]
|
335
|
+
if remaining_parts:
|
336
|
+
import json
|
337
|
+
first_data = json.loads(first_part)
|
338
|
+
extra_parts = []
|
339
|
+
for part in remaining_parts:
|
340
|
+
if isinstance(part, str) and not part.startswith('{"'):
|
341
|
+
extra_parts.append(f'"{part}"')
|
342
|
+
else:
|
343
|
+
extra_parts.append(part)
|
344
|
+
|
345
|
+
extra_json = ",".join(extra_parts)
|
346
|
+
return f'tellraw @a {{"text":"{first_data["text"]}","extra":[{extra_json}]}}'
|
347
|
+
else:
|
348
|
+
return f'tellraw @a {first_part}'
|
349
|
+
|
350
|
+
def _if_statement_to_command(self, if_stmt: IfStatement) -> str:
|
351
|
+
"""Convert if statement to comment and include actual statements."""
|
352
|
+
condition = self._expression_to_condition(if_stmt.condition)
|
353
|
+
lines = [f"# if {condition}"]
|
354
|
+
|
355
|
+
# Include the actual statements from the if body for visibility
|
356
|
+
for stmt in if_stmt.then_body:
|
357
|
+
if isinstance(stmt, VariableAssignment):
|
358
|
+
# Include variable assignments directly
|
359
|
+
cmd = self._variable_assignment_to_command(stmt)
|
360
|
+
lines.append(cmd)
|
361
|
+
elif isinstance(stmt, SayCommand):
|
362
|
+
# Include say commands directly
|
363
|
+
cmd = self._say_command_to_command(stmt)
|
364
|
+
lines.append(cmd)
|
365
|
+
elif isinstance(stmt, RawBlock):
|
366
|
+
# Include raw blocks directly
|
367
|
+
lines.append(stmt.content)
|
368
|
+
elif isinstance(stmt, IfStatement):
|
369
|
+
# Recursively handle nested if statements
|
370
|
+
cmd = self._if_statement_to_command(stmt)
|
371
|
+
lines.append(cmd)
|
372
|
+
elif isinstance(stmt, WhileLoop):
|
373
|
+
# Handle while loops
|
374
|
+
cmd = self._while_loop_to_command(stmt)
|
375
|
+
lines.append(cmd)
|
376
|
+
elif isinstance(stmt, FunctionCall):
|
377
|
+
# Handle function calls
|
378
|
+
cmd = self._function_call_to_command(stmt)
|
379
|
+
lines.append(cmd)
|
380
|
+
|
381
|
+
# Handle else body if it exists
|
382
|
+
if if_stmt.else_body:
|
383
|
+
lines.append("")
|
384
|
+
lines.append("# else:")
|
385
|
+
for stmt in if_stmt.else_body:
|
386
|
+
if isinstance(stmt, VariableAssignment):
|
387
|
+
cmd = self._variable_assignment_to_command(stmt)
|
388
|
+
lines.append(cmd)
|
389
|
+
elif isinstance(stmt, SayCommand):
|
390
|
+
cmd = self._say_command_to_command(stmt)
|
391
|
+
lines.append(cmd)
|
392
|
+
elif isinstance(stmt, RawBlock):
|
393
|
+
lines.append(stmt.content)
|
394
|
+
elif isinstance(stmt, IfStatement):
|
395
|
+
cmd = self._if_statement_to_command(stmt)
|
396
|
+
lines.append(cmd)
|
397
|
+
elif isinstance(stmt, WhileLoop):
|
398
|
+
cmd = self._while_loop_to_command(stmt)
|
399
|
+
lines.append(cmd)
|
400
|
+
elif isinstance(stmt, FunctionCall):
|
401
|
+
cmd = self._function_call_to_command(stmt)
|
402
|
+
lines.append(cmd)
|
403
|
+
|
404
|
+
return "\n".join(lines)
|
405
|
+
|
406
|
+
def _while_loop_to_command(self, while_loop: WhileLoop) -> str:
|
407
|
+
"""Convert while loop to comment and include actual statements."""
|
408
|
+
condition = self._expression_to_condition(while_loop.condition)
|
409
|
+
lines = [f"# while {condition}"]
|
410
|
+
|
411
|
+
# Include the actual statements from the while body for visibility
|
412
|
+
for stmt in while_loop.body:
|
413
|
+
if isinstance(stmt, VariableAssignment):
|
414
|
+
# Include variable assignments directly
|
415
|
+
cmd = self._variable_assignment_to_command(stmt)
|
416
|
+
lines.append(cmd)
|
417
|
+
elif isinstance(stmt, SayCommand):
|
418
|
+
# Include say commands directly
|
419
|
+
cmd = self._say_command_to_command(stmt)
|
420
|
+
lines.append(cmd)
|
421
|
+
elif isinstance(stmt, RawBlock):
|
422
|
+
# Include raw blocks directly
|
423
|
+
lines.append(stmt.content)
|
424
|
+
elif isinstance(stmt, IfStatement):
|
425
|
+
# Recursively handle nested if statements
|
426
|
+
cmd = self._if_statement_to_command(stmt)
|
427
|
+
lines.append(cmd)
|
428
|
+
elif isinstance(stmt, WhileLoop):
|
429
|
+
# Handle nested while loops
|
430
|
+
cmd = self._while_loop_to_command(stmt)
|
431
|
+
lines.append(cmd)
|
432
|
+
elif isinstance(stmt, FunctionCall):
|
433
|
+
# Handle function calls
|
434
|
+
cmd = self._function_call_to_command(stmt)
|
435
|
+
lines.append(cmd)
|
436
|
+
|
437
|
+
return "\n".join(lines)
|
438
|
+
|
439
|
+
def _function_call_to_command(self, func_call: FunctionCall) -> str:
|
440
|
+
"""Convert function call to execute command."""
|
441
|
+
if func_call.scope:
|
442
|
+
return f"execute as {func_call.scope.strip('<>')} run function {func_call.namespace}:{func_call.name}"
|
443
|
+
else:
|
444
|
+
return f"function {func_call.namespace}:{func_call.name}"
|
445
|
+
|
446
|
+
def _expression_to_value(self, expression: Any) -> str:
|
447
|
+
"""Convert expression to a value string."""
|
448
|
+
if isinstance(expression, LiteralExpression):
|
449
|
+
return str(expression.value)
|
450
|
+
elif isinstance(expression, VariableSubstitution):
|
451
|
+
objective = self.variables.get(expression.name, expression.name)
|
452
|
+
scope = expression.scope.strip("<>")
|
453
|
+
return f"score {scope} {objective}"
|
454
|
+
elif isinstance(expression, BinaryExpression):
|
455
|
+
left = self._expression_to_value(expression.left)
|
456
|
+
right = self._expression_to_value(expression.right)
|
457
|
+
return f"{left} {expression.operator} {right}"
|
458
|
+
elif isinstance(expression, ParenthesizedExpression):
|
459
|
+
return f"({self._expression_to_value(expression.expression)})"
|
460
|
+
else:
|
461
|
+
return str(expression)
|
462
|
+
|
463
|
+
def _expression_to_condition(self, expression: Any) -> str:
|
464
|
+
"""Convert expression to a condition string."""
|
465
|
+
if isinstance(expression, BinaryExpression):
|
466
|
+
left = self._expression_to_value(expression.left)
|
467
|
+
right = self._expression_to_value(expression.right)
|
468
|
+
return f"{left} {expression.operator} {right}"
|
469
|
+
else:
|
470
|
+
return self._expression_to_value(expression)
|
@@ -185,6 +185,19 @@ class MDLCompilationError(MDLError):
|
|
185
185
|
return f"Compilation Error: {super().__str__()}"
|
186
186
|
|
187
187
|
|
188
|
+
@dataclass
|
189
|
+
class MDLCompilerError(MDLError):
|
190
|
+
"""Error during compilation process."""
|
191
|
+
error_type: str = "compiler_error"
|
192
|
+
|
193
|
+
def __str__(self) -> str:
|
194
|
+
try:
|
195
|
+
from .cli_colors import color
|
196
|
+
return f"{color.error_type('Compiler Error:')} {super().__str__()}"
|
197
|
+
except ImportError:
|
198
|
+
return f"Compiler Error: {super().__str__()}"
|
199
|
+
|
200
|
+
|
188
201
|
@dataclass
|
189
202
|
class MDLFileError(MDLError):
|
190
203
|
"""Error related to file operations."""
|
@@ -302,6 +315,7 @@ def create_error(error_type: str, message: str, file_path: Optional[str] = None,
|
|
302
315
|
"validation": MDLValidationError,
|
303
316
|
"build": MDLBuildError,
|
304
317
|
"compilation": MDLCompilationError,
|
318
|
+
"compiler": MDLCompilerError,
|
305
319
|
"file": MDLFileError,
|
306
320
|
"configuration": MDLConfigurationError
|
307
321
|
}
|