minecraft-datapack-language 16.0.19__py3-none-any.whl → 16.0.21__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.
@@ -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 = '16.0.19'
32
- __version_tuple__ = version_tuple = (16, 0, 19)
31
+ __version__ = version = '16.0.21'
32
+ __version_tuple__ = version_tuple = (16, 0, 21)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -33,6 +33,8 @@ class MDLCompiler:
33
33
  self.declared_variables: List[VariableDeclaration] = []
34
34
  # Track any temporary scoreboard variables generated during compilation
35
35
  self.temp_variables: Set[str] = set()
36
+ # Track if global scope is used anywhere
37
+ self.uses_global_scope: bool = False
36
38
 
37
39
  def compile(self, ast: Program, source_dir: str = None) -> str:
38
40
  """Compile MDL AST into a complete Minecraft datapack."""
@@ -174,6 +176,10 @@ class MDLCompiler:
174
176
  lines.append(self._ensure_macro_prefix(cmd))
175
177
  # Done routing temp commands for this function body
176
178
  self._temp_sink_stack.pop()
179
+
180
+ # If global scope is used anywhere in the program, prepend ensure line here too
181
+ if self.uses_global_scope:
182
+ self._insert_ensure_global(lines)
177
183
 
178
184
  return "\n".join(lines)
179
185
 
@@ -302,6 +308,11 @@ class MDLCompiler:
302
308
  "# Generated by MDL Compiler",
303
309
  ""
304
310
  ]
311
+
312
+ # Ensure a single global armor stand exists if global scope is used
313
+ if self.uses_global_scope:
314
+ lines.extend(self._ensure_global_lines())
315
+ lines.append("")
305
316
 
306
317
  # Add scoreboard objective creation for variables
307
318
  for var_name, objective in self.variables.items():
@@ -774,6 +785,9 @@ class MDLCompiler:
774
785
  else:
775
786
  functions_dir = self.output_dir / "data" / self.current_namespace / "functions"
776
787
  functions_dir.mkdir(parents=True, exist_ok=True)
788
+ # Insert ensure global if needed
789
+ if self.uses_global_scope:
790
+ self._insert_ensure_global(lines)
777
791
  func_file = functions_dir / f"{name}.mcfunction"
778
792
  with open(func_file, 'w') as f:
779
793
  f.write("\n".join(lines) + "\n")
@@ -789,7 +803,7 @@ class MDLCompiler:
789
803
 
790
804
  base = f"function {func_call.namespace}:{func_call.name}{suffix}"
791
805
  if func_call.scope:
792
- return f"execute as {func_call.scope.strip('<>')} run {base}"
806
+ return f"execute as {self._resolve_scope(func_call.scope)} run {base}"
793
807
  return base
794
808
 
795
809
  def _expression_to_value(self, expression: Any) -> str:
@@ -807,7 +821,7 @@ class MDLCompiler:
807
821
  return str(expression.value)
808
822
  elif isinstance(expression, VariableSubstitution):
809
823
  objective = self.variables.get(expression.name, expression.name)
810
- scope = expression.scope.strip("<>")
824
+ scope = self._resolve_scope(expression.scope)
811
825
  return f"score {scope} {objective}"
812
826
  elif isinstance(expression, UnaryExpression):
813
827
  # Handle logical NOT elsewhere; here support unary minus for arithmetic
@@ -983,7 +997,7 @@ class MDLCompiler:
983
997
  # Variable vs literal
984
998
  if op_sym and isinstance(left, VariableSubstitution) and isinstance(right, LiteralExpression) and isinstance(right.value, (int, float)):
985
999
  objective = self.variables.get(left.name, left.name)
986
- scope = left.scope.strip("<>")
1000
+ scope = self._resolve_scope(left.scope)
987
1001
  # Normalize number
988
1002
  try:
989
1003
  v = float(right.value)
@@ -1025,9 +1039,9 @@ class MDLCompiler:
1025
1039
  # Variable vs variable
1026
1040
  if op_sym and isinstance(left, VariableSubstitution) and isinstance(right, VariableSubstitution):
1027
1041
  lobj = self.variables.get(left.name, left.name)
1028
- lscope = left.scope.strip("<>")
1042
+ lscope = self._resolve_scope(left.scope)
1029
1043
  robj = self.variables.get(right.name, right.name)
1030
- rscope = right.scope.strip("<>")
1044
+ rscope = self._resolve_scope(right.scope)
1031
1045
  if op_sym in (">", ">=", "<", "<=", "=="):
1032
1046
  comp = op_sym if op_sym != "==" else "="
1033
1047
  return (f"score {lscope} {lobj} {comp} {rscope} {robj}", False)
@@ -1057,6 +1071,39 @@ class MDLCompiler:
1057
1071
  # Fallback: treat as generic condition string
1058
1072
  return (self._expression_to_condition(expression), False)
1059
1073
 
1074
+ def _resolve_scope(self, scope_spec: Optional[str]) -> str:
1075
+ """Resolve MDL scope spec to a Minecraft selector string.
1076
+ - None or missing angle brackets defaults to stripping.
1077
+ - <global> maps to the singleton mdl_global armor stand.
1078
+ """
1079
+ if not scope_spec:
1080
+ return "@s"
1081
+ s = scope_spec.strip()
1082
+ if s == "<global>":
1083
+ self.uses_global_scope = True
1084
+ return "@e[type=armor_stand,tag=mdl_global,limit=1]"
1085
+ if s.startswith("<") and s.endswith(">"):
1086
+ return s[1:-1]
1087
+ # Fallback: already looks like a selector
1088
+ return s
1089
+
1090
+ def _ensure_global_lines(self) -> list:
1091
+ return [
1092
+ "# Ensure global armor stand exists",
1093
+ "execute unless entity @e[type=armor_stand,tag=mdl_global,limit=1] run summon armor_stand ~ ~ ~ {NoGravity:1b,Invulnerable:1b,Invisible:1b,Tags:[\"mdl_global\"]}",
1094
+ ]
1095
+
1096
+ def _insert_ensure_global(self, lines: list):
1097
+ # Insert ensure lines after header/comments at top if not already present
1098
+ ensure = self._ensure_global_lines()[0]
1099
+ if any(ensure in l for l in lines):
1100
+ return
1101
+ # Insert after initial comment block
1102
+ insert_at = 0
1103
+ for i, l in enumerate(lines[:5]):
1104
+ insert_at = i + 1
1105
+ lines[insert_at:insert_at] = self._ensure_global_lines() + [""]
1106
+
1060
1107
  def _compile_boolean_expression(self, expression: Any, out_var: Optional[str] = None) -> str:
1061
1108
  """Compile a logical expression into a temporary boolean scoreboard variable (1 true, 0 false).
1062
1109
  Returns the objective name for the boolean temp variable.
@@ -1137,7 +1184,8 @@ class MDLCompiler:
1137
1184
  obj = parts[2]
1138
1185
  self._store_temp_command(f"scoreboard players operation @s {temp_var} = {scope} {obj}")
1139
1186
  else:
1140
- self._store_temp_command(f"scoreboard players set @s {temp_var} {left_value}")
1187
+ lit = self._normalize_integer_literal_string(str(left_value), ctx="addition left operand")
1188
+ self._store_temp_command(f"scoreboard players set @s {temp_var} {lit}")
1141
1189
  # Add right value
1142
1190
  if isinstance(expression.right, VariableSubstitution) or (isinstance(right_value, str) and right_value.startswith("score ")):
1143
1191
  parts = str(right_value).split()
@@ -1145,7 +1193,13 @@ class MDLCompiler:
1145
1193
  obj = parts[2]
1146
1194
  self._store_temp_command(f"scoreboard players operation @s {temp_var} += {scope} {obj}")
1147
1195
  else:
1148
- self._store_temp_command(f"scoreboard players add @s {temp_var} {right_value}")
1196
+ lit = self._normalize_integer_literal_string(str(right_value), ctx="addition right operand")
1197
+ if lit == "0":
1198
+ pass
1199
+ elif lit.startswith("-"):
1200
+ self._store_temp_command(f"scoreboard players remove @s {temp_var} {lit[1:]}")
1201
+ else:
1202
+ self._store_temp_command(f"scoreboard players add @s {temp_var} {lit}")
1149
1203
 
1150
1204
  elif expression.operator == "MINUS":
1151
1205
  if isinstance(expression.left, BinaryExpression):
@@ -1157,7 +1211,8 @@ class MDLCompiler:
1157
1211
  obj = parts[2]
1158
1212
  self._store_temp_command(f"scoreboard players operation @s {temp_var} = {scope} {obj}")
1159
1213
  else:
1160
- self._store_temp_command(f"scoreboard players set @s {temp_var} {left_value}")
1214
+ lit = self._normalize_integer_literal_string(str(left_value), ctx="subtraction left operand")
1215
+ self._store_temp_command(f"scoreboard players set @s {temp_var} {lit}")
1161
1216
  # Subtract right value
1162
1217
  if isinstance(expression.right, VariableSubstitution) or (isinstance(right_value, str) and right_value.startswith("score ")):
1163
1218
  parts = str(right_value).split()
@@ -1165,7 +1220,14 @@ class MDLCompiler:
1165
1220
  obj = parts[2]
1166
1221
  self._store_temp_command(f"scoreboard players operation @s {temp_var} -= {scope} {obj}")
1167
1222
  else:
1168
- self._store_temp_command(f"scoreboard players remove @s {temp_var} {right_value}")
1223
+ lit = self._normalize_integer_literal_string(str(right_value), ctx="subtraction right operand")
1224
+ if lit == "0":
1225
+ pass
1226
+ elif lit.startswith("-"):
1227
+ # x - (-k) == x + k
1228
+ self._store_temp_command(f"scoreboard players add @s {temp_var} {lit[1:]}")
1229
+ else:
1230
+ self._store_temp_command(f"scoreboard players remove @s {temp_var} {lit}")
1169
1231
 
1170
1232
  elif expression.operator == "MULTIPLY":
1171
1233
  if isinstance(expression.left, BinaryExpression):
@@ -1185,8 +1247,13 @@ class MDLCompiler:
1185
1247
  # For literal values, keep explicit multiply command for compatibility
1186
1248
  if isinstance(expression.right, LiteralExpression):
1187
1249
  # Normalize number formatting (e.g., 2.0 -> 2)
1188
- literal_str = self._expression_to_value(expression.right)
1189
- self._store_temp_command(f"scoreboard players multiply @s {temp_var} {literal_str}")
1250
+ literal_str = self._normalize_integer_literal_string(self._expression_to_value(expression.right), ctx="multiply literal")
1251
+ if literal_str == "0":
1252
+ self._store_temp_command(f"scoreboard players set @s {temp_var} 0")
1253
+ elif literal_str == "1":
1254
+ pass
1255
+ else:
1256
+ self._store_temp_command(f"scoreboard players multiply @s {temp_var} {literal_str}")
1190
1257
  else:
1191
1258
  # If right_value is a score reference string, strip the leading 'score '
1192
1259
  if isinstance(right_value, str) and right_value.startswith("score "):
@@ -1198,7 +1265,17 @@ class MDLCompiler:
1198
1265
  else:
1199
1266
  self._store_temp_command(f"scoreboard players operation @s {temp_var} *= {right_value}")
1200
1267
  else:
1201
- self._store_temp_command(f"scoreboard players operation @s {temp_var} *= {right_value}")
1268
+ # If RHS is a numeric literal string (incl. negatives), use multiply
1269
+ if self._is_numeric_literal_string(right_value):
1270
+ lit = self._normalize_integer_literal_string(str(right_value), ctx="multiply literal string")
1271
+ if lit == "0":
1272
+ self._store_temp_command(f"scoreboard players set @s {temp_var} 0")
1273
+ elif lit == "1":
1274
+ pass
1275
+ else:
1276
+ self._store_temp_command(f"scoreboard players multiply @s {temp_var} {lit}")
1277
+ else:
1278
+ self._store_temp_command(f"scoreboard players operation @s {temp_var} *= {right_value}")
1202
1279
 
1203
1280
  elif expression.operator == "DIVIDE":
1204
1281
  if isinstance(expression.left, BinaryExpression):
@@ -1218,8 +1295,17 @@ class MDLCompiler:
1218
1295
  # For literal values, keep explicit divide command for compatibility
1219
1296
  if isinstance(expression.right, LiteralExpression):
1220
1297
  # Normalize number formatting (e.g., 2.0 -> 2)
1221
- literal_str = self._expression_to_value(expression.right)
1222
- self._store_temp_command(f"scoreboard players divide @s {temp_var} {literal_str}")
1298
+ lit = self._normalize_integer_literal_string(self._expression_to_value(expression.right), ctx="divide literal")
1299
+ if lit == "0":
1300
+ raise MDLCompilerError("Division by zero literal is not allowed", "Use a non-zero integer literal")
1301
+ if lit == "1":
1302
+ pass
1303
+ elif lit.startswith("-"):
1304
+ abs_lit = lit[1:]
1305
+ self._store_temp_command(f"scoreboard players divide @s {temp_var} {abs_lit}")
1306
+ self._store_temp_command(f"scoreboard players multiply @s {temp_var} -1")
1307
+ else:
1308
+ self._store_temp_command(f"scoreboard players divide @s {temp_var} {lit}")
1223
1309
  else:
1224
1310
  # If right_value is a score reference string, strip the leading 'score '
1225
1311
  if isinstance(right_value, str) and right_value.startswith("score "):
@@ -1231,7 +1317,21 @@ class MDLCompiler:
1231
1317
  else:
1232
1318
  self._store_temp_command(f"scoreboard players operation @s {temp_var} /= {right_value}")
1233
1319
  else:
1234
- self._store_temp_command(f"scoreboard players operation @s {temp_var} /= {right_value}")
1320
+ # If RHS is a numeric literal string (incl. negatives), use divide
1321
+ if self._is_numeric_literal_string(right_value):
1322
+ lit = self._normalize_integer_literal_string(str(right_value), ctx="divide literal string")
1323
+ if lit == "0":
1324
+ raise MDLCompilerError("Division by zero literal is not allowed", "Use a non-zero integer literal")
1325
+ if lit == "1":
1326
+ pass
1327
+ elif lit.startswith("-"):
1328
+ abs_lit = lit[1:]
1329
+ self._store_temp_command(f"scoreboard players divide @s {temp_var} {abs_lit}")
1330
+ self._store_temp_command(f"scoreboard players multiply @s {temp_var} -1")
1331
+ else:
1332
+ self._store_temp_command(f"scoreboard players divide @s {temp_var} {lit}")
1333
+ else:
1334
+ self._store_temp_command(f"scoreboard players operation @s {temp_var} /= {right_value}")
1235
1335
  else:
1236
1336
  # For other operators, just set the value
1237
1337
  self._store_temp_command(f"scoreboard players set @s {temp_var} 0")
@@ -1243,6 +1343,36 @@ class MDLCompiler:
1243
1343
  else:
1244
1344
  # Fallback: do nothing, but keep behavior predictable
1245
1345
  pass
1346
+
1347
+ def _is_numeric_literal_string(self, s: Any) -> bool:
1348
+ """Return True if s is a string representing an integer literal (incl. negatives)."""
1349
+ if not isinstance(s, str):
1350
+ return False
1351
+ try:
1352
+ # Allow floats that are integer-valued like "2.0" by casting then checking is_integer
1353
+ if "." in s:
1354
+ f = float(s)
1355
+ return f.is_integer()
1356
+ int(s)
1357
+ return True
1358
+ except Exception:
1359
+ return False
1360
+
1361
+ def _normalize_integer_literal_string(self, s: str, ctx: str = "") -> str:
1362
+ """Normalize a numeric literal string to an integer string, or raise MDLCompilerError.
1363
+ Accepts negatives and floats that are mathematically integers (e.g., "2.0").
1364
+ """
1365
+ try:
1366
+ f = float(s)
1367
+ except Exception:
1368
+ raise MDLCompilerError(f"Expected integer literal, got '{s}'", f"Invalid numeric literal in {ctx}")
1369
+ if not float(f).is_integer():
1370
+ raise MDLCompilerError(f"Scoreboards are integer-only; got non-integer '{s}'", f"Use integers in {ctx}")
1371
+ n = int(f)
1372
+ # Normalize -0 to 0
1373
+ if n == 0:
1374
+ return "0"
1375
+ return str(n)
1246
1376
 
1247
1377
  def _generate_temp_variable_name(self) -> str:
1248
1378
  """Generate a unique temporary variable name."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: minecraft-datapack-language
3
- Version: 16.0.19
3
+ Version: 16.0.21
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
@@ -1,18 +1,18 @@
1
1
  minecraft_datapack_language/__init__.py,sha256=0KVXBE4ScRaRUrf83aA2tVB-y8A_jplyaxVvtHH6Uw0,1199
2
- minecraft_datapack_language/_version.py,sha256=Y9AKuL_9dQTuwAdAm-tOXFpJ2Ltnsjm1_Nklu8wputY,708
2
+ minecraft_datapack_language/_version.py,sha256=XlIY3WMs9CiPI4MdJIsAr0hOM97V2wv7a_weiKPC39E,708
3
3
  minecraft_datapack_language/ast_nodes.py,sha256=L5izavSeXDr766vsfRvJrcnflXNJyXcy0WSfyJPq2ZA,4484
4
4
  minecraft_datapack_language/cli.py,sha256=shmQtNNXgw5XNlGquAR52wWtYeNyYusZTwwCZl56mMU,9522
5
5
  minecraft_datapack_language/dir_map.py,sha256=HmxFkuvWGkzHF8o_GFb4BpuMCRc6QMw8UbmcAI8JVdY,1788
6
- minecraft_datapack_language/mdl_compiler.py,sha256=AA3SZC7k8O1UbhlY6uEaxCVNsDK6jlPf5HDdi-OS0hk,63367
6
+ minecraft_datapack_language/mdl_compiler.py,sha256=-nqxEbo6Pk_zo8cMDKmkIhz8vOVIRGqoESWHSgDOmD8,70118
7
7
  minecraft_datapack_language/mdl_errors.py,sha256=r0Gu3KhoX1YLPAVW_iO7Q_fPgaf_Dv9tOGSOdKNSzmw,16114
8
8
  minecraft_datapack_language/mdl_lexer.py,sha256=YuRflOkoMOcjECPAZzoAkJciMks5amWMtGbcTIVKmAs,24166
9
9
  minecraft_datapack_language/mdl_linter.py,sha256=z85xoAglENurCh30bR7kEHZ_JeMxcYaLDcGNRAl-RAI,17253
10
10
  minecraft_datapack_language/mdl_parser.py,sha256=vIcPRudxDaezNy85Q5CBcumLhCglofCNITsrRmj9YWw,27302
11
11
  minecraft_datapack_language/python_api.py,sha256=Iao1jbdeW6ekeA80BZG6gNqHVjxQJEheB3DbpVsuTZQ,12304
12
12
  minecraft_datapack_language/utils.py,sha256=Aq0HAGlXqj9BUTEjaEilpvzEW0EtZYYMMwOqG9db6dE,684
13
- minecraft_datapack_language-16.0.19.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
14
- minecraft_datapack_language-16.0.19.dist-info/METADATA,sha256=s1mTXRppWEMJFAMWDt7k3poHbZC1gL1n0WFp6JdB4T8,8344
15
- minecraft_datapack_language-16.0.19.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
- minecraft_datapack_language-16.0.19.dist-info/entry_points.txt,sha256=c6vjBeCiyQflvPHBRyBk2nJCSfYt3Oc7Sc9V87ySi_U,108
17
- minecraft_datapack_language-16.0.19.dist-info/top_level.txt,sha256=ADtFI476tbKLLxEAA-aJQAfg53MA3k_DOb0KTFiggfw,28
18
- minecraft_datapack_language-16.0.19.dist-info/RECORD,,
13
+ minecraft_datapack_language-16.0.21.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
14
+ minecraft_datapack_language-16.0.21.dist-info/METADATA,sha256=dW8AbMzdXeJPw755mPSuPl-7PjXkgcuCHX_QN-RKWAs,8344
15
+ minecraft_datapack_language-16.0.21.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
+ minecraft_datapack_language-16.0.21.dist-info/entry_points.txt,sha256=c6vjBeCiyQflvPHBRyBk2nJCSfYt3Oc7Sc9V87ySi_U,108
17
+ minecraft_datapack_language-16.0.21.dist-info/top_level.txt,sha256=ADtFI476tbKLLxEAA-aJQAfg53MA3k_DOb0KTFiggfw,28
18
+ minecraft_datapack_language-16.0.21.dist-info/RECORD,,