minecraft-datapack-language 15.4.40__py3-none-any.whl → 15.4.42__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 = '15.4.40'
32
- __version_tuple__ = version_tuple = (15, 4, 40)
31
+ __version__ = version = '15.4.42'
32
+ __version_tuple__ = version_tuple = (15, 4, 42)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -74,6 +74,9 @@ class FunctionCall(ASTNode):
74
74
  namespace: str
75
75
  name: str
76
76
  scope: Optional[str] # Optional scope for the function call
77
+ # Macro invocation support (Minecraft function macros)
78
+ macro_json: Optional[str] = None # Inline compound JSON string (including braces)
79
+ with_clause: Optional[str] = None # Raw "with <data source> [path]" clause (without leading 'with')
77
80
 
78
81
 
79
82
  @dataclass
@@ -132,6 +135,12 @@ class ScoreboardCommand(ASTNode):
132
135
  command: str
133
136
 
134
137
 
138
+ @dataclass
139
+ class MacroLine(ASTNode):
140
+ """A raw macro line for mcfunction starting with '$' and containing $(vars)."""
141
+ content: str
142
+
143
+
135
144
  # Expression nodes
136
145
  @dataclass
137
146
  class BinaryExpression(ASTNode):
@@ -11,7 +11,7 @@ from typing import Dict, List, Any, Optional
11
11
  from .ast_nodes import (
12
12
  Program, PackDeclaration, NamespaceDeclaration, TagDeclaration,
13
13
  VariableDeclaration, VariableAssignment, VariableSubstitution, FunctionDeclaration,
14
- FunctionCall, IfStatement, WhileLoop, HookDeclaration, RawBlock,
14
+ FunctionCall, IfStatement, WhileLoop, HookDeclaration, RawBlock, MacroLine,
15
15
  SayCommand, BinaryExpression, LiteralExpression, ParenthesizedExpression
16
16
  )
17
17
  from .dir_map import get_dir_map, DirMap
@@ -325,6 +325,8 @@ class MDLCompiler:
325
325
  return self._say_command_to_command(statement)
326
326
  elif isinstance(statement, RawBlock):
327
327
  return statement.content
328
+ elif isinstance(statement, MacroLine):
329
+ return statement.content
328
330
  elif isinstance(statement, IfStatement):
329
331
  return self._if_statement_to_command(statement)
330
332
  elif isinstance(statement, WhileLoop):
@@ -616,10 +618,17 @@ class MDLCompiler:
616
618
 
617
619
  def _function_call_to_command(self, func_call: FunctionCall) -> str:
618
620
  """Convert function call to execute command."""
621
+ # Build base function invocation, possibly with macro args
622
+ suffix = ""
623
+ if func_call.macro_json:
624
+ suffix = f" {func_call.macro_json}"
625
+ elif func_call.with_clause:
626
+ suffix = f" with {func_call.with_clause}"
627
+
628
+ base = f"function {func_call.namespace}:{func_call.name}{suffix}"
619
629
  if func_call.scope:
620
- return f"execute as {func_call.scope.strip('<>')} run function {func_call.namespace}:{func_call.name}"
621
- else:
622
- return f"function {func_call.namespace}:{func_call.name}"
630
+ return f"execute as {func_call.scope.strip('<>')} run {base}"
631
+ return base
623
632
 
624
633
  def _expression_to_value(self, expression: Any) -> str:
625
634
  """Convert expression to a value string."""
@@ -79,6 +79,7 @@ class TokenType:
79
79
  QUOTE = "QUOTE" # " (string literal delimiter)
80
80
  EXCLAMATION = "EXCLAMATION" # ! (for raw blocks)
81
81
  RANGE = "RANGE" # .. (range operator)
82
+ DOT = "DOT" # . (for paths in with-clause)
82
83
 
83
84
  # Literals
84
85
  IDENTIFIER = "IDENTIFIER" # Variable names, function names, etc.
@@ -89,6 +90,7 @@ class TokenType:
89
90
  EOF = "EOF"
90
91
  COMMENT = "COMMENT" # Comments (ignored during parsing)
91
92
  RAW_CONTENT = "RAW_CONTENT" # Raw content inside raw blocks
93
+ MACRO_LINE = "MACRO_LINE" # Entire macro line starting with '$' at line-begin
92
94
 
93
95
 
94
96
  class MDLLexer:
@@ -167,9 +169,9 @@ class MDLLexer:
167
169
  self._scan_multi_line_comment()
168
170
  return
169
171
 
170
- # Handle strings (quotes)
171
- if char == '"':
172
- self._scan_string()
172
+ # Handle strings (quotes) - support both ' and "
173
+ if char == '"' or char == "'":
174
+ self._scan_string(quote_char=char)
173
175
  return
174
176
 
175
177
  # Handle raw block markers
@@ -180,6 +182,11 @@ class MDLLexer:
180
182
 
181
183
 
182
184
 
185
+ # Handle macro line: '$' as first non-space on the line (not $!raw)
186
+ if char == '$' and self._is_line_start_nonspace():
187
+ self._scan_macro_line()
188
+ return
189
+
183
190
  # Handle variable substitution
184
191
  if char == '$':
185
192
  self._scan_variable_substitution()
@@ -264,7 +271,7 @@ class MDLLexer:
264
271
  # Unterminated comment
265
272
  self._error("Unterminated multi-line comment", "Add */ to close the comment")
266
273
 
267
- def _scan_string(self):
274
+ def _scan_string(self, quote_char='"'):
268
275
  """Scan a string literal (quoted text)."""
269
276
  # Skip opening quote
270
277
  self.current += 1
@@ -275,7 +282,7 @@ class MDLLexer:
275
282
 
276
283
  # Scan until closing quote
277
284
  while (self.current < len(self.source) and
278
- self.source[self.current] != '"'):
285
+ self.source[self.current] != quote_char):
279
286
  if self.source[self.current] == '\n':
280
287
  self._error("Unterminated string literal", "Add a closing quote")
281
288
 
@@ -295,14 +302,34 @@ class MDLLexer:
295
302
  self.column += 1
296
303
 
297
304
  # Generate QUOTE token for the opening quote
298
- self.tokens.append(Token(TokenType.QUOTE, '"', start_line, start_column))
305
+ self.tokens.append(Token(TokenType.QUOTE, quote_char, start_line, start_column))
299
306
 
300
307
  # Generate IDENTIFIER token for the string content
301
308
  string_content = self.source[self.start + 1:self.current - 1]
302
309
  self.tokens.append(Token(TokenType.IDENTIFIER, string_content, start_line, start_column + 1))
303
310
 
304
311
  # Generate QUOTE token for the closing quote
305
- self.tokens.append(Token(TokenType.QUOTE, '"', self.line, self.column - 1))
312
+ self.tokens.append(Token(TokenType.QUOTE, quote_char, self.line, self.column - 1))
313
+
314
+ def _is_line_start_nonspace(self) -> bool:
315
+ """Return True if current position is at the first non-space character in the line."""
316
+ # Find beginning of current line
317
+ idx = self.current - 1
318
+ while idx >= 0 and self.source[idx] != '\n':
319
+ if not self.source[idx].isspace():
320
+ return False
321
+ idx -= 1
322
+ return True
323
+
324
+ def _scan_macro_line(self):
325
+ """Scan a full macro line starting with '$' as first non-space char."""
326
+ # Capture from current to end of line (excluding trailing newline)
327
+ line_start = self.current
328
+ while self.current < len(self.source) and self.source[self.current] != '\n':
329
+ self.current += 1
330
+ self.column += 1
331
+ content = self.source[line_start:self.current]
332
+ self.tokens.append(Token(TokenType.MACRO_LINE, content, self.line, 1))
306
333
 
307
334
  def _scan_raw_block_start(self):
308
335
  """Scan the start of a raw block ($!raw)."""
@@ -548,7 +575,8 @@ class MDLLexer:
548
575
  '{': TokenType.LBRACE,
549
576
  '}': TokenType.RBRACE,
550
577
  '[': TokenType.LBRACKET,
551
- ']': TokenType.RBRACKET
578
+ ']': TokenType.RBRACKET,
579
+ '.': TokenType.DOT
552
580
  }
553
581
 
554
582
  if char in token_map:
@@ -9,7 +9,7 @@ from .mdl_errors import MDLParserError
9
9
  from .ast_nodes import (
10
10
  ASTNode, Program, PackDeclaration, NamespaceDeclaration, TagDeclaration,
11
11
  VariableDeclaration, VariableAssignment, VariableSubstitution, FunctionDeclaration,
12
- FunctionCall, IfStatement, WhileLoop, HookDeclaration, RawBlock,
12
+ FunctionCall, IfStatement, WhileLoop, HookDeclaration, RawBlock, MacroLine,
13
13
  SayCommand, TellrawCommand, ExecuteCommand, ScoreboardCommand,
14
14
  BinaryExpression, UnaryExpression, ParenthesizedExpression, LiteralExpression,
15
15
  ScopeSelector
@@ -257,7 +257,7 @@ class MDLParser:
257
257
  )
258
258
 
259
259
  def _parse_function_call(self) -> FunctionCall:
260
- """Parse function call: exec namespace:name<scope>;"""
260
+ """Parse function call: exec namespace:name<scope>? [ '{json}' | with <data source> [path] ] ;"""
261
261
  self._expect(TokenType.EXEC, "Expected 'exec' keyword")
262
262
 
263
263
  # Parse namespace:name
@@ -269,13 +269,41 @@ class MDLParser:
269
269
  scope = None
270
270
  if self._peek().type == TokenType.LANGLE:
271
271
  scope = self._parse_scope_selector()
272
-
272
+
273
+ # Optional macro arguments (inline JSON in a quoted string)
274
+ macro_json = None
275
+ with_clause = None
276
+ if self._peek().type == TokenType.QUOTE:
277
+ self._advance() # opening quote
278
+ if self._peek().type == TokenType.IDENTIFIER:
279
+ macro_json = self._peek().value
280
+ self._advance()
281
+ self._expect(TokenType.QUOTE, "Expected closing quote for macro JSON")
282
+ elif self._peek().type == TokenType.IDENTIFIER and self._peek().value == 'with':
283
+ # Capture everything after 'with' up to the semicolon as raw clause
284
+ self._advance() # consume 'with'
285
+ # Expect a data source spec like: storage <identifier> <path-with-dots>
286
+ # Accumulate tokens until semicolon, inserting spaces only between identifiers/numbers
287
+ built: List[str] = []
288
+ prev_type = None
289
+ while not self._is_at_end() and self._peek().type != TokenType.SEMICOLON:
290
+ t = self._advance()
291
+ # Insert a space between adjacent identifiers/numbers
292
+ if built and (prev_type in (TokenType.IDENTIFIER, TokenType.NUMBER, TokenType.RBRACE, TokenType.RBRACKET)
293
+ and t.type in (TokenType.IDENTIFIER, TokenType.NUMBER)):
294
+ built.append(" ")
295
+ built.append(t.value)
296
+ prev_type = t.type
297
+ with_clause = "".join(built).strip()
298
+
273
299
  self._expect(TokenType.SEMICOLON, "Expected semicolon after function call")
274
300
 
275
301
  return FunctionCall(
276
302
  namespace=namespace,
277
303
  name=name,
278
- scope=scope
304
+ scope=scope,
305
+ macro_json=macro_json,
306
+ with_clause=with_clause
279
307
  )
280
308
 
281
309
  def _parse_if_statement(self) -> IfStatement:
@@ -498,6 +526,10 @@ class MDLParser:
498
526
  statements.append(self._parse_while_loop())
499
527
  elif self._peek().type == TokenType.EXEC:
500
528
  statements.append(self._parse_function_call())
529
+ elif self._peek().type == TokenType.MACRO_LINE:
530
+ # Preserve macro line exactly as-is
531
+ statements.append(MacroLine(content=self._peek().value))
532
+ self._advance()
501
533
  elif self._peek().type == TokenType.DOLLAR and self._peek(1).type == TokenType.EXCLAMATION:
502
534
  statements.append(self._parse_raw_block())
503
535
  elif self._peek().type == TokenType.IDENTIFIER:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: minecraft-datapack-language
3
- Version: 15.4.40
3
+ Version: 15.4.42
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
@@ -146,17 +146,17 @@ namespace "game";
146
146
  var num player_score<@s> = 0;
147
147
  var num team_score<@a[team=red]> = 0;
148
148
 
149
- function game:start<@s> {
149
+ function game:start {
150
150
  player_score<@s> = 100;
151
151
  say "Welcome! Your score is $player_score<@s>$";
152
152
  }
153
153
 
154
- on_load game:start<@s>;
154
+ on_load game:start;
155
155
  ```
156
156
 
157
157
  ### Control Structures
158
158
  ```mdl
159
- function game:check_score<@s> {
159
+ function game:check_score {
160
160
  if $player_score<@s>$ > 10 {
161
161
  say "Great score!";
162
162
  player_score<@s> = $player_score<@s>$ + 5;
@@ -185,7 +185,7 @@ tag structure "wizard_tower" "structures/wizard_tower.json";
185
185
 
186
186
  ### Raw Blocks and Say Commands
187
187
  ```mdl
188
- function game:special_effect<@s> {
188
+ function game:special_effect {
189
189
  $!raw
190
190
  execute as @s run particle minecraft:explosion ~ ~ ~ 1 1 1 0 10
191
191
  execute as @s run playsound minecraft:entity.player.levelup player @s ~ ~ ~ 1 1
@@ -0,0 +1,18 @@
1
+ minecraft_datapack_language/__init__.py,sha256=0KVXBE4ScRaRUrf83aA2tVB-y8A_jplyaxVvtHH6Uw0,1199
2
+ minecraft_datapack_language/_version.py,sha256=ABeTqXifUkxtZdjz6siLkCIP2z8wQH4fvvWWue5JRi4,708
3
+ minecraft_datapack_language/ast_nodes.py,sha256=UzUxKLkjBisUd5Gu7sAiNXIIPIjNoRzELq4LfIFcnSY,4290
4
+ minecraft_datapack_language/cli.py,sha256=R4QZYtox-Da9B8pr_kCg_9qc9aI-ORTah7kMkhsI5tw,10373
5
+ minecraft_datapack_language/dir_map.py,sha256=HmxFkuvWGkzHF8o_GFb4BpuMCRc6QMw8UbmcAI8JVdY,1788
6
+ minecraft_datapack_language/mdl_compiler.py,sha256=-7UKB-VkFvca3RKmSEuzqyj_4wEemPXnL8PRlNea8ZQ,41719
7
+ minecraft_datapack_language/mdl_errors.py,sha256=r0Gu3KhoX1YLPAVW_iO7Q_fPgaf_Dv9tOGSOdKNSzmw,16114
8
+ minecraft_datapack_language/mdl_lexer.py,sha256=dVwdgUmdQI7EJaFtoKZpq6rj5156YVjt44mMee-MDIs,23388
9
+ minecraft_datapack_language/mdl_linter.py,sha256=z85xoAglENurCh30bR7kEHZ_JeMxcYaLDcGNRAl-RAI,17253
10
+ minecraft_datapack_language/mdl_parser.py,sha256=axyjdrcgqeOpbmWolasiIZAjV_RpFaP5QiafLPXAhms,25223
11
+ minecraft_datapack_language/python_api.py,sha256=Iao1jbdeW6ekeA80BZG6gNqHVjxQJEheB3DbpVsuTZQ,12304
12
+ minecraft_datapack_language/utils.py,sha256=Aq0HAGlXqj9BUTEjaEilpvzEW0EtZYYMMwOqG9db6dE,684
13
+ minecraft_datapack_language-15.4.42.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
14
+ minecraft_datapack_language-15.4.42.dist-info/METADATA,sha256=C0MQeYXEQ_Z-vpg6fSS9GsjcDVEpmKnl2m4GuAtBw9U,8344
15
+ minecraft_datapack_language-15.4.42.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
+ minecraft_datapack_language-15.4.42.dist-info/entry_points.txt,sha256=c6vjBeCiyQflvPHBRyBk2nJCSfYt3Oc7Sc9V87ySi_U,108
17
+ minecraft_datapack_language-15.4.42.dist-info/top_level.txt,sha256=ADtFI476tbKLLxEAA-aJQAfg53MA3k_DOb0KTFiggfw,28
18
+ minecraft_datapack_language-15.4.42.dist-info/RECORD,,
@@ -1,18 +0,0 @@
1
- minecraft_datapack_language/__init__.py,sha256=0KVXBE4ScRaRUrf83aA2tVB-y8A_jplyaxVvtHH6Uw0,1199
2
- minecraft_datapack_language/_version.py,sha256=P2VVYhMLipXcQKyw7Y1OEvR4mKshcYtZAmzVwV3FZfs,708
3
- minecraft_datapack_language/ast_nodes.py,sha256=nbWrRz137MGMRpmnq8QkXNzrtlaCgyPEknytbkrS_M8,3899
4
- minecraft_datapack_language/cli.py,sha256=R4QZYtox-Da9B8pr_kCg_9qc9aI-ORTah7kMkhsI5tw,10373
5
- minecraft_datapack_language/dir_map.py,sha256=HmxFkuvWGkzHF8o_GFb4BpuMCRc6QMw8UbmcAI8JVdY,1788
6
- minecraft_datapack_language/mdl_compiler.py,sha256=CaIHmsv4cjZa70PpderdgPlH6yNaB86WQhGyIaB0040,41396
7
- minecraft_datapack_language/mdl_errors.py,sha256=r0Gu3KhoX1YLPAVW_iO7Q_fPgaf_Dv9tOGSOdKNSzmw,16114
8
- minecraft_datapack_language/mdl_lexer.py,sha256=CjbEUpuuF4eU_ucA_WIhw6wSMcHGk2BchtQ0bLAGvwg,22033
9
- minecraft_datapack_language/mdl_linter.py,sha256=z85xoAglENurCh30bR7kEHZ_JeMxcYaLDcGNRAl-RAI,17253
10
- minecraft_datapack_language/mdl_parser.py,sha256=aQPKcmATM9BOMzO7vCXmMdxU1qjOJNLCSAKJopu5h3g,23429
11
- minecraft_datapack_language/python_api.py,sha256=Iao1jbdeW6ekeA80BZG6gNqHVjxQJEheB3DbpVsuTZQ,12304
12
- minecraft_datapack_language/utils.py,sha256=Aq0HAGlXqj9BUTEjaEilpvzEW0EtZYYMMwOqG9db6dE,684
13
- minecraft_datapack_language-15.4.40.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
14
- minecraft_datapack_language-15.4.40.dist-info/METADATA,sha256=KIF4J5dORym2e-a7jCryY_SRCkqq5tyPxtCgC5vVFBA,8360
15
- minecraft_datapack_language-15.4.40.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
- minecraft_datapack_language-15.4.40.dist-info/entry_points.txt,sha256=c6vjBeCiyQflvPHBRyBk2nJCSfYt3Oc7Sc9V87ySi_U,108
17
- minecraft_datapack_language-15.4.40.dist-info/top_level.txt,sha256=ADtFI476tbKLLxEAA-aJQAfg53MA3k_DOb0KTFiggfw,28
18
- minecraft_datapack_language-15.4.40.dist-info/RECORD,,