minecraft-datapack-language 17.0.10__py3-none-any.whl → 17.0.12__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/_embedded/docs/404.html +42 -0
- minecraft_datapack_language/_embedded/docs/Gemfile +26 -0
- minecraft_datapack_language/_embedded/docs/README.md +84 -0
- minecraft_datapack_language/_embedded/docs/_config.yml +74 -0
- minecraft_datapack_language/_embedded/docs/_data/version.yml +3 -0
- minecraft_datapack_language/_embedded/docs/_docs/cli-reference.md +506 -0
- minecraft_datapack_language/_embedded/docs/_docs/contributing.md +352 -0
- minecraft_datapack_language/_embedded/docs/_docs/docs-hub.md +266 -0
- minecraft_datapack_language/_embedded/docs/_docs/documentation.md +135 -0
- minecraft_datapack_language/_embedded/docs/_docs/examples.md +194 -0
- minecraft_datapack_language/_embedded/docs/_docs/getting-started.md +230 -0
- minecraft_datapack_language/_embedded/docs/_docs/language-reference.md +1637 -0
- minecraft_datapack_language/_embedded/docs/_docs/multi-file-projects.md +221 -0
- minecraft_datapack_language/_embedded/docs/_docs/python-bindings.md +446 -0
- minecraft_datapack_language/_embedded/docs/_docs/vscode-extension.md +381 -0
- minecraft_datapack_language/_embedded/docs/_includes/head-custom.html +983 -0
- minecraft_datapack_language/_embedded/docs/_includes/navigation.html +362 -0
- minecraft_datapack_language/_embedded/docs/_layouts/default.html +27 -0
- minecraft_datapack_language/_embedded/docs/_layouts/page.html +281 -0
- minecraft_datapack_language/_embedded/docs/_plugins/test_version.rb +13 -0
- minecraft_datapack_language/_embedded/docs/_plugins/version_reader.rb +37 -0
- minecraft_datapack_language/_embedded/docs/assets/css/style.css +211 -0
- minecraft_datapack_language/_embedded/docs/docs.md +134 -0
- minecraft_datapack_language/_embedded/docs/downloads.md +444 -0
- minecraft_datapack_language/_embedded/docs/icons/favicon-16.png +0 -0
- minecraft_datapack_language/_embedded/docs/icons/favicon-32.png +0 -0
- minecraft_datapack_language/_embedded/docs/icons/favicon-48.png +0 -0
- minecraft_datapack_language/_embedded/docs/icons/favicon-64.png +0 -0
- minecraft_datapack_language/_embedded/docs/icons/icon-1024.png +0 -0
- minecraft_datapack_language/_embedded/docs/icons/icon-128.png +0 -0
- minecraft_datapack_language/_embedded/docs/icons/icon-256.png +0 -0
- minecraft_datapack_language/_embedded/docs/icons/icon-512.png +0 -0
- minecraft_datapack_language/_embedded/docs/icons/icon-64.png +0 -0
- minecraft_datapack_language/_embedded/docs/index.md +378 -0
- minecraft_datapack_language/_version.py +2 -2
- minecraft_datapack_language/cli.py +5 -1
- {minecraft_datapack_language-17.0.10.dist-info → minecraft_datapack_language-17.0.12.dist-info}/METADATA +1 -1
- minecraft_datapack_language-17.0.12.dist-info/RECORD +56 -0
- minecraft_datapack_language-17.0.10.dist-info/RECORD +0 -22
- {minecraft_datapack_language-17.0.10.dist-info → minecraft_datapack_language-17.0.12.dist-info}/WHEEL +0 -0
- {minecraft_datapack_language-17.0.10.dist-info → minecraft_datapack_language-17.0.12.dist-info}/entry_points.txt +0 -0
- {minecraft_datapack_language-17.0.10.dist-info → minecraft_datapack_language-17.0.12.dist-info}/licenses/LICENSE +0 -0
- {minecraft_datapack_language-17.0.10.dist-info → minecraft_datapack_language-17.0.12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1637 @@
|
|
1
|
+
---
|
2
|
+
layout: page
|
3
|
+
title: Language Reference
|
4
|
+
permalink: /docs/language-reference/
|
5
|
+
---
|
6
|
+
|
7
|
+
# MDL (Minecraft Datapack Language) - Complete Language Reference
|
8
|
+
|
9
|
+
MDL is a simple, scope-aware language that compiles to Minecraft datapack `.mcfunction` files. This document defines the complete language specification.
|
10
|
+
|
11
|
+
## Core Language Design
|
12
|
+
|
13
|
+
### Philosophy
|
14
|
+
- **Explicit scoping**: Variables support explicit `<scope>`; if omitted, `@s` (current entity) is assumed
|
15
|
+
- **Clear reading vs writing**: Use `$variable<scope>$` or `$variable$` for reading, and `variable<scope>` or `variable` for writing
|
16
|
+
- **No scope inheritance**: Each operation uses its own explicitly defined scope (or defaults to `@s` when omitted)
|
17
|
+
- **Default scope**: When no scope specified, always use `@s` (current entity)
|
18
|
+
- **No return values**: All functions are void - they execute commands and modify state
|
19
|
+
- **No quotes needed**: Use `$variable<scope>$` syntax directly instead of string literals
|
20
|
+
- **Function execution**: Use `exec` keyword to execute all functions
|
21
|
+
- **Tag-based resources**: Use tag syntax to reference datapack resources like recipes, loot tables, etc.
|
22
|
+
- **User-friendly communication**: `say` commands automatically convert to `tellraw` with proper JSON formatting
|
23
|
+
- **Real control flow**: If/else if/else statements and while loops that actually work and generate proper Minecraft conditional logic
|
24
|
+
|
25
|
+
## Basic Syntax
|
26
|
+
|
27
|
+
### Pack Declaration
|
28
|
+
```mdl
|
29
|
+
pack "pack_name" "description" pack_format;
|
30
|
+
```
|
31
|
+
|
32
|
+
### Namespace Declaration
|
33
|
+
```mdl
|
34
|
+
namespace "namespace_name";
|
35
|
+
```
|
36
|
+
|
37
|
+
### Tag Declarations
|
38
|
+
```mdl
|
39
|
+
// Recipe tags
|
40
|
+
tag recipe "RecipeName" "path/to/recipe.json";
|
41
|
+
tag recipe "diamond_sword" "recipes/diamond_sword.json";
|
42
|
+
|
43
|
+
// Loot table tags
|
44
|
+
tag loot_table "LootTableName" "path/to/loot_table.json";
|
45
|
+
tag loot_table "epic_loot" "loot_tables/epic_loot.json";
|
46
|
+
|
47
|
+
// Advancement tags
|
48
|
+
tag advancement "AdvancementName" "path/to/advancement.json";
|
49
|
+
tag advancement "first_spell" "advancements/first_spell.json";
|
50
|
+
|
51
|
+
// Item modifier tags
|
52
|
+
tag item_modifier "ItemModifierName" "path/to/item_modifier.json";
|
53
|
+
tag item_modifier "enchant_tool" "item_modifiers/enchant_tool.json";
|
54
|
+
|
55
|
+
// Predicate tags
|
56
|
+
tag predicate "PredicateName" "path/to/predicate.json";
|
57
|
+
tag predicate "has_mana" "predicates/has_mana.json";
|
58
|
+
|
59
|
+
// Structure tags
|
60
|
+
tag structure "StructureName" "path/to/structure.json";
|
61
|
+
tag structure "custom_house" "structures/custom_house.json";
|
62
|
+
```
|
63
|
+
|
64
|
+
### Variable Declaration
|
65
|
+
```mdl
|
66
|
+
// Declare variables (scope optional; defaults to @s)
|
67
|
+
var num player_score<@a> = 0; // Global scope - accessible by all players
|
68
|
+
var num player_health<@s> = 20; // Player-specific scope
|
69
|
+
var num player_health = 20; // Same as player_health<@s> = 20
|
70
|
+
var num team_score<@a[team=red]> = 0; // Team scope
|
71
|
+
var num entity_data<@e[type=armor_stand,tag=mdl_global,limit=1]> = 0; // Custom entity scope
|
72
|
+
```
|
73
|
+
|
74
|
+
### Variable Assignment
|
75
|
+
```mdl
|
76
|
+
// Scope optional; defaults to @s for both reads and writes when omitted
|
77
|
+
player_score<@s> = $player_score<@s>$ + 1; // Add 1 to current player's score
|
78
|
+
player_health<@a> = $player_health<@s>$; // Read from @s, write to @a
|
79
|
+
team_score<@a[team=red]> = 5; // Set red team score to 5
|
80
|
+
|
81
|
+
// Default scope is @s when not specified
|
82
|
+
player_score = 0; // Same as player_score<@s> = 0;
|
83
|
+
```
|
84
|
+
|
85
|
+
### Variable Substitution
|
86
|
+
```mdl
|
87
|
+
// Use $variable<scope>$ or $variable$ anywhere in the code
|
88
|
+
// $variable$ defaults to <@s>
|
89
|
+
tellraw @s {"text":"You have ","extra":[{"score":{"name":"@s","objective":"player_score"}}," points"]};
|
90
|
+
tellraw @s {"text":"You have ","extra":[{"score":{"name":"@s","objective":"player_score"}}," points"]}; // $player_score$
|
91
|
+
execute if score @s player_score matches 10.. run game:celebrate;
|
92
|
+
|
93
|
+
// In conditions
|
94
|
+
if $player_score$ > 10 {
|
95
|
+
player_score = 0; // defaults to <@s>
|
96
|
+
}
|
97
|
+
```
|
98
|
+
|
99
|
+
### Say Commands (Auto-converted to tellraw)
|
100
|
+
```mdl
|
101
|
+
// Simple say commands automatically convert to tellraw with JSON formatting
|
102
|
+
say "Welcome to the game!";
|
103
|
+
say "You have $player_score<@s>$ points!";
|
104
|
+
say "Team score: $team_score<@a[team=red]>$";
|
105
|
+
|
106
|
+
// These get converted to:
|
107
|
+
// tellraw @a {"text":"Welcome to the game!"};
|
108
|
+
// tellraw @a {"text":"You have ","extra":[{"score":{"name":"@s","objective":"player_score"}}," points!"]};
|
109
|
+
// tellraw @a {"text":"Team score: ","extra":[{"score":{"name":"@s","objective":"team_score"}}]};
|
110
|
+
```
|
111
|
+
|
112
|
+
### Functions
|
113
|
+
|
114
|
+
#### Function Declaration
|
115
|
+
```mdl
|
116
|
+
// Basic function
|
117
|
+
function game:start_game {
|
118
|
+
player_score<@s> = 0;
|
119
|
+
player_health<@s> = 20;
|
120
|
+
}
|
121
|
+
|
122
|
+
// Function declaration (no scope on definition)
|
123
|
+
function game:reset_player {
|
124
|
+
player_score<@s> = 0;
|
125
|
+
player_health<@s> = 20;
|
126
|
+
}
|
127
|
+
```
|
128
|
+
|
129
|
+
#### Function Calls
|
130
|
+
```mdl
|
131
|
+
// Execute function with exec keyword (runs any function, with or without scope)
|
132
|
+
exec game:reset_player; // Execute function
|
133
|
+
exec game:start_game; // Execute any function
|
134
|
+
exec utils:calculator; // Execute from different namespace
|
135
|
+
exec game:reset_player<@s>; // Execute function with scope
|
136
|
+
exec game:reset_player<@a>; // Execute function with different scope
|
137
|
+
|
138
|
+
// Function Macros (Minecraft snapshot): pass macro arguments
|
139
|
+
// Inline JSON compound as a single-quoted string to minimize escapes
|
140
|
+
exec game:spawn_mob '{id:"minecraft:cow",name:"Betsy"}';
|
141
|
+
// With-clause to pull a compound from a data source
|
142
|
+
exec game:spawn_mob with storage mymod:ctx path.to.compound;
|
143
|
+
```
|
144
|
+
|
145
|
+
### Exec and Scope Execution Rules
|
146
|
+
- `exec ns:name` runs `function ns:name` in the current executor context.
|
147
|
+
- `exec ns:name<selector>` compiles to `execute as <selector> run function ns:name`.
|
148
|
+
- Macro args compile to `function ns:name {json}` form; with-clause compiles to `function ns:name with <data source and path>`.
|
149
|
+
|
150
|
+
### Control Structures
|
151
|
+
|
152
|
+
#### If Statements
|
153
|
+
```mdl
|
154
|
+
if $player_score<@s>$ > 10 {
|
155
|
+
exec game:celebrate;
|
156
|
+
player_score<@s> = 0;
|
157
|
+
}
|
158
|
+
|
159
|
+
if $player_health<@s>$ < 5 {
|
160
|
+
exec game:heal;
|
161
|
+
} else {
|
162
|
+
exec game:check_health;
|
163
|
+
}
|
164
|
+
```
|
165
|
+
|
166
|
+
#### Else If Statements
|
167
|
+
```mdl
|
168
|
+
if $player_score<@s>$ > 100 {
|
169
|
+
exec game:celebrate;
|
170
|
+
player_score<@s> = 0;
|
171
|
+
} else if $player_score<@s>$ > 50 {
|
172
|
+
exec game:reward;
|
173
|
+
player_score<@s> = $player_score<@s>$ + 10;
|
174
|
+
} else {
|
175
|
+
exec game:encourage;
|
176
|
+
player_score<@s>$ = $player_score<@s>$ + 5;
|
177
|
+
}
|
178
|
+
```
|
179
|
+
|
180
|
+
#### While Loops
|
181
|
+
```mdl
|
182
|
+
while $counter<@s>$ > 0 {
|
183
|
+
counter<@s> = $counter<@s>$ - 1;
|
184
|
+
exec game:countdown;
|
185
|
+
}
|
186
|
+
```
|
187
|
+
|
188
|
+
**Note:** The standard `while` loop uses recursive function calls internally. This is simple and fast but can hit Minecraft's function call depth limit for very long-running loops.
|
189
|
+
|
190
|
+
#### Scheduled While Loops
|
191
|
+
|
192
|
+
Use `scheduledwhile` to iterate via the scheduler instead of recursion. This avoids recursion limits by running one iteration per game tick and scheduling the next iteration only if the condition remains true.
|
193
|
+
|
194
|
+
```mdl
|
195
|
+
scheduledwhile $counter<@s>$ > 0 {
|
196
|
+
counter<@s> = $counter<@s>$ - 1;
|
197
|
+
exec game:countdown;
|
198
|
+
}
|
199
|
+
```
|
200
|
+
|
201
|
+
Compilation strategy:
|
202
|
+
- Generates a helper function containing the loop body
|
203
|
+
- At the end of the helper, emits `execute if <condition> run schedule function <helper> 1t`
|
204
|
+
- Entry point schedules the first iteration with `schedule function <helper> 1t`
|
205
|
+
- Breakout occurs naturally when the condition becomes false (no re-schedule)
|
206
|
+
|
207
|
+
When to use:
|
208
|
+
- Prefer `while` for short/medium loops
|
209
|
+
- Prefer `scheduledwhile` for long-running loops, per-tick processes, or when avoiding recursion depth limits
|
210
|
+
|
211
|
+
### Hooks
|
212
|
+
```mdl
|
213
|
+
on_load game:start_game; // Runs when datapack loads
|
214
|
+
on_tick game:update_timer; // Runs every tick
|
215
|
+
```
|
216
|
+
|
217
|
+
**Note:** Hooks use the same function reference syntax as regular function calls, but they are processed at datapack load time, not during execution.
|
218
|
+
|
219
|
+
### Raw Blocks
|
220
|
+
```mdl
|
221
|
+
// Raw blocks pass through unchanged - no MDL processing
|
222
|
+
$!raw
|
223
|
+
scoreboard players set @s player_timer_enabled 1
|
224
|
+
execute as @a run function game:increase_tick_per_player
|
225
|
+
say "Raw commands bypass MDL syntax checking"
|
226
|
+
raw!$
|
227
|
+
|
228
|
+
// Single-line raw commands
|
229
|
+
$!raw scoreboard players add @s player_tick_counter 1 raw!$
|
230
|
+
|
231
|
+
// Raw blocks can contain any Minecraft commands, including complex execute chains
|
232
|
+
$!raw
|
233
|
+
execute as @a[team=red] at @s run particle minecraft:explosion ~ ~ ~ 1 1 1 0 10
|
234
|
+
execute as @a[team=blue] at @s run playsound minecraft:entity.player.levelup player @s ~ ~ ~ 1 1
|
235
|
+
raw!$
|
236
|
+
```
|
237
|
+
|
238
|
+
### Macro Lines
|
239
|
+
```mdl
|
240
|
+
// Lines starting with $ are emitted as-is into the generated .mcfunction and
|
241
|
+
// can contain $(variable) placeholders that Minecraft will substitute when the
|
242
|
+
// function is called with a macro compound.
|
243
|
+
$summon minecraft:cow ~ ~ ~ {CustomName:'{"text":"$(name)"}'}
|
244
|
+
```
|
245
|
+
|
246
|
+
**Important:** Raw blocks are completely ignored by the MDL parser. They get copied directly to the output `.mcfunction` files without any processing. This means you can use any valid Minecraft command syntax inside raw blocks.
|
247
|
+
|
248
|
+
## Scope System
|
249
|
+
|
250
|
+
### Core Scope Rules
|
251
|
+
|
252
|
+
1. **Variable Writing**: Use `variable<scope>` for assignments and declarations; `variable` defaults to `<@s>`
|
253
|
+
2. **Variable Reading**: Use `$variable<scope>$` for reading values; `$variable$` defaults to `<@s>`
|
254
|
+
3. **Function Execution**: Use `exec` keyword to run any function (with or without scope)
|
255
|
+
4. **No Inheritance**: Functions do not inherit scope from their caller
|
256
|
+
5. **Default Scope**: When no scope specified, always use `@s` (current entity)
|
257
|
+
6. **No Memory**: The system does not remember a variable's declared scope for subsequent operations
|
258
|
+
|
259
|
+
### Scope Usage Examples
|
260
|
+
|
261
|
+
```mdl
|
262
|
+
// VARIABLES: Clear distinction between reading and writing
|
263
|
+
var num score<@a> = 0; // Declare with scope
|
264
|
+
score<@s> = 5; // Write with scope
|
265
|
+
if $score<@a>$ > 10 { ... } // Read with scope
|
266
|
+
|
267
|
+
// FUNCTIONS: Use exec keyword to run any function (with or without scope)
|
268
|
+
exec game:start; // Execute function
|
269
|
+
exec utils:helper; // Execute from different namespace
|
270
|
+
exec game:start<@a>; // Execute function with scope
|
271
|
+
```
|
272
|
+
|
273
|
+
### Scope Examples
|
274
|
+
|
275
|
+
```mdl
|
276
|
+
// Declare variable with global scope
|
277
|
+
var num global_counter<@a> = 0;
|
278
|
+
|
279
|
+
// Later operations - each specifies its own scope
|
280
|
+
global_counter<@s> = 5; // Set current player's counter to 5
|
281
|
+
global_counter<@a> = $global_counter<@a>$ + 1; // Increment global counter
|
282
|
+
global_counter = 10; // Same as global_counter<@s> = 10 (defaults to @s)
|
283
|
+
say "Player has $global_counter$ points"; // $global_counter$ defaults to <@s>
|
284
|
+
|
285
|
+
// Function calls
|
286
|
+
exec game:increment; // Execute function
|
287
|
+
exec game:increment<@s>; // Execute function with scope
|
288
|
+
exec utils:helper; // Execute from different namespace
|
289
|
+
```
|
290
|
+
|
291
|
+
### Valid Scope Selectors
|
292
|
+
|
293
|
+
```mdl
|
294
|
+
// Basic selectors
|
295
|
+
<@s> // Current player
|
296
|
+
<@a> // All players
|
297
|
+
<@p> // Nearest player
|
298
|
+
<@r> // Random player
|
299
|
+
|
300
|
+
// Complex selectors
|
301
|
+
<@a[team=red]> // Red team players
|
302
|
+
<@e[type=armor_stand,tag=mdl_global,limit=1]> // Specific entity
|
303
|
+
<@s[distance=..5]> // Current player within 5 blocks
|
304
|
+
|
305
|
+
// Global scope (special case)
|
306
|
+
<global> // Maps to @e[type=armor_stand,tag=mdl_global,limit=1]
|
307
|
+
// A single invisible armor stand with tag 'mdl_global' is ensured on load
|
308
|
+
```
|
309
|
+
|
310
|
+
## Mathematical Expressions
|
311
|
+
|
312
|
+
### Operators
|
313
|
+
```mdl
|
314
|
+
// Arithmetic
|
315
|
+
+ (addition)
|
316
|
+
- (subtraction)
|
317
|
+
* (multiplication)
|
318
|
+
/ (division)
|
319
|
+
|
320
|
+
// Comparison
|
321
|
+
== (equal)
|
322
|
+
!= (not equal)
|
323
|
+
> (greater than)
|
324
|
+
< (less than)
|
325
|
+
>= (greater than or equal)
|
326
|
+
<= (less than or equal)
|
327
|
+
|
328
|
+
// Logical
|
329
|
+
&& (logical AND)
|
330
|
+
|| (logical OR)
|
331
|
+
! (logical NOT)
|
332
|
+
|
333
|
+
// Range (for matches)
|
334
|
+
.. (range operator)
|
335
|
+
```
|
336
|
+
|
337
|
+
### Unary Operators and Precedence
|
338
|
+
- Unary minus: `-x` applies before multiplication/division and addition/subtraction. Literals are constant-folded; non-literals are compiled as `0 - x` via a temp score.
|
339
|
+
- Logical NOT: `!expr` negates a boolean expression. For comparisons like `!$a$ > 0`, the comparison is compiled first, then inverted using `execute unless`.
|
340
|
+
- Precedence (lowest to highest):
|
341
|
+
1) `||`
|
342
|
+
2) `&&`
|
343
|
+
3) Comparisons (`>`, `>=`, `<`, `<=`, `==`, `!=`)
|
344
|
+
4) `+`, `-`
|
345
|
+
5) `*`, `/`
|
346
|
+
6) Unary (`!`, unary `-`)
|
347
|
+
7) Parentheses `(...)`
|
348
|
+
|
349
|
+
### Expression Examples
|
350
|
+
```mdl
|
351
|
+
// Complex expressions with different scopes
|
352
|
+
player_score<@s> = $x<@a>$ + $y<@p>$ * $z<@r>$;
|
353
|
+
|
354
|
+
// Parentheses for precedence
|
355
|
+
player_score<@s> = ($x<@s>$ + $y<@s>$) * 2;
|
356
|
+
|
357
|
+
// Comparisons
|
358
|
+
if $score<@s>$ > 10 {
|
359
|
+
exec game:reward;
|
360
|
+
}
|
361
|
+
|
362
|
+
// Logical operators
|
363
|
+
if $a<@s>$ > 0 && $b<@s>$ > 0 {
|
364
|
+
say "Both are greater than 0";
|
365
|
+
}
|
366
|
+
|
367
|
+
if $a<@s>$ > 0 || $b<@s>$ > 0 {
|
368
|
+
say "At least one is greater than 0";
|
369
|
+
}
|
370
|
+
|
371
|
+
// NOT negates the entire comparison when used like: !$a$ > 0
|
372
|
+
if !$a$ > 0 {
|
373
|
+
say "a is not greater than 0";
|
374
|
+
}
|
375
|
+
|
376
|
+
// Complex logical expression with parentheses
|
377
|
+
if ($a$ > 0 && $b$ > 0) || $c$ > 0 {
|
378
|
+
say "Condition satisfied";
|
379
|
+
}
|
380
|
+
// Unary minus with literals and variables
|
381
|
+
var num t = -2;
|
382
|
+
if ($x$ + -($y$ * 3)) >= -5 { say "ok"; }
|
383
|
+
```
|
384
|
+
|
385
|
+
## Reserved Names
|
386
|
+
|
387
|
+
### Function Names to Avoid
|
388
|
+
- `load` - Conflicts with Minecraft's built-in load function
|
389
|
+
- `tick` - Conflicts with Minecraft's built-in tick function
|
390
|
+
- Any other names that might conflict with Minecraft's internal functions
|
391
|
+
|
392
|
+
### Alternative Naming
|
393
|
+
```mdl
|
394
|
+
// Instead of 'load', use:
|
395
|
+
function game:initialize { ... }
|
396
|
+
function game:setup { ... }
|
397
|
+
function game:start { ... }
|
398
|
+
|
399
|
+
// Instead of 'tick', use:
|
400
|
+
function game:update { ... }
|
401
|
+
function game:loop { ... }
|
402
|
+
function game:process { ... }
|
403
|
+
```
|
404
|
+
|
405
|
+
## Complete Examples
|
406
|
+
|
407
|
+
### Basic Counter with Tags
|
408
|
+
```mdl
|
409
|
+
pack "counter" "Counter example" 82;
|
410
|
+
namespace "counter";
|
411
|
+
|
412
|
+
// Tag declarations
|
413
|
+
tag recipe "diamond_sword" "recipes/diamond_sword.json";
|
414
|
+
tag loot_table "sword_loot" "loot_tables/sword_loot.json";
|
415
|
+
tag advancement "first_sword" "advancements/first_sword.json";
|
416
|
+
|
417
|
+
var num global_counter<@a> = 0;
|
418
|
+
var num player_counter<@s> = 0;
|
419
|
+
|
420
|
+
function "increment" {
|
421
|
+
global_counter<@a> = $global_counter<@a>$ + 1;
|
422
|
+
player_counter<@s> = $player_counter<@s>$ + 1;
|
423
|
+
|
424
|
+
// Using tellraw for player-specific messages
|
425
|
+
tellraw @s {"text":"Global: ","extra":[{"score":{"name":"@s","objective":"global_counter"}}," Player: ",{"score":{"name":"@s","objective":"player_counter"}}]};
|
426
|
+
|
427
|
+
// Using say for broadcast messages (auto-converts to tellraw)
|
428
|
+
say "Player $player_counter<@s>$ just incremented the counter!";
|
429
|
+
}
|
430
|
+
|
431
|
+
function "reset_player" {
|
432
|
+
player_counter<@s> = 0;
|
433
|
+
tellraw @s {"text":"Counter reset!"};
|
434
|
+
}
|
435
|
+
|
436
|
+
on_load "counter:increment";
|
437
|
+
```
|
438
|
+
|
439
|
+
### Team Game with Resources
|
440
|
+
```mdl
|
441
|
+
pack "teamgame" "Team game example" 82;
|
442
|
+
namespace "teamgame";
|
443
|
+
|
444
|
+
// Tag declarations
|
445
|
+
tag recipe "team_banner" "recipes/team_banner.json";
|
446
|
+
tag loot_table "team_reward" "loot_tables/team_reward.json";
|
447
|
+
tag advancement "team_win" "advancements/team_win.json";
|
448
|
+
tag item_modifier "team_boost" "item_modifiers/team_boost.json";
|
449
|
+
|
450
|
+
var num red_score<@a[team=red]> = 0;
|
451
|
+
var num blue_score<@a[team=blue]> = 0;
|
452
|
+
var num player_score<@s> = 0;
|
453
|
+
|
454
|
+
function "award_points" {
|
455
|
+
player_score<@s> = $player_score<@s>$ + 10;
|
456
|
+
|
457
|
+
if $player_score<@s>$ > 100 {
|
458
|
+
red_score<@a[team=red]> = $red_score<@a[team=red]>$ + 10;
|
459
|
+
tellraw @s {"text":"High score bonus! Red team score: ","extra":[{"score":{"name":"@s","objective":"red_score"}}]};
|
460
|
+
} else if $player_score<@s>$ > 50 {
|
461
|
+
red_score<@a[team=red]> = $red_score<@a[team=red]>$ + 5;
|
462
|
+
tellraw @s {"text":"Medium score bonus! Red team score: ","extra":[{"score":{"name":"@s","objective":"red_score"}}]};
|
463
|
+
} else {
|
464
|
+
red_score<@a[team=red]> = $red_score<@a[team=red]>$ + 1;
|
465
|
+
tellraw @s {"text":"Standard bonus! Red team score: ","extra":[{"score":{"name":"@s","objective":"red_score"}}]};
|
466
|
+
}
|
467
|
+
|
468
|
+
tellraw @s {"text":"Your score: ","extra":[{"score":{"name":"@s","objective":"player_score"}}]};
|
469
|
+
}
|
470
|
+
|
471
|
+
function "show_leaderboard" {
|
472
|
+
tellraw @s {"text":"=== LEADERBOARD ==="};
|
473
|
+
tellraw @s {"text":"Red Team: ","extra":[{"score":{"name":"@s","objective":"red_score"}}]};
|
474
|
+
tellraw @s {"text":"Blue Team: ","extra":[{"score":{"name":"@s","objective":"blue_score"}}]};
|
475
|
+
tellraw @s {"text":"Your Score: ","extra":[{"score":{"name":"@s","objective":"player_score"}}]};
|
476
|
+
}
|
477
|
+
|
478
|
+
function "countdown_timer" {
|
479
|
+
var num timer<@s> = 10;
|
480
|
+
|
481
|
+
while $timer<@s>$ > 0 {
|
482
|
+
tellraw @s {"text":"Time remaining: ","extra":[{"score":{"name":"@s","objective":"timer"}}]};
|
483
|
+
timer<@s> = $timer<@s>$ - 1;
|
484
|
+
exec game:wait_one_second;
|
485
|
+
}
|
486
|
+
|
487
|
+
tellraw @s {"text":"Time's up!"};
|
488
|
+
}
|
489
|
+
```
|
490
|
+
|
491
|
+
### Complex Game Logic
|
492
|
+
```mdl
|
493
|
+
pack "game" "Complex game example" 82;
|
494
|
+
namespace "game";
|
495
|
+
|
496
|
+
// Tag declarations
|
497
|
+
tag recipe "magic_wand" "recipes/magic_wand.json";
|
498
|
+
tag loot_table "magic_loot" "loot_tables/magic_loot.json";
|
499
|
+
tag advancement "magic_master" "advancements/magic_master.json";
|
500
|
+
tag predicate "has_mana" "predicates/has_mana.json";
|
501
|
+
tag structure "magic_tower" "structures/magic_tower.json";
|
502
|
+
|
503
|
+
var num player_level<@s> = 1;
|
504
|
+
var num player_exp<@s> = 0;
|
505
|
+
var num global_high_score<@a> = 0;
|
506
|
+
var num game_timer<@a> = 0;
|
507
|
+
|
508
|
+
function "gain_experience" {
|
509
|
+
player_exp<@s> = $player_exp<@s>$ + 10;
|
510
|
+
|
511
|
+
if $player_exp<@s>$ >= 100 {
|
512
|
+
player_level<@s> = $player_level<@s>$ + 1;
|
513
|
+
player_exp<@s> = 0;
|
514
|
+
tellraw @s {"text":"Level up! New level: ","extra":[{"score":{"name":"@s","objective":"player_level"}}]};
|
515
|
+
|
516
|
+
if $player_level<@s>$ > $global_high_score<@a>$ {
|
517
|
+
global_high_score<@a> = $player_level<@s>$;
|
518
|
+
tellraw @a {"text":"New high level achieved: ","extra":[{"score":{"name":"@s","objective":"global_high_score"}}]};
|
519
|
+
}
|
520
|
+
}
|
521
|
+
}
|
522
|
+
|
523
|
+
function "update_timer" {
|
524
|
+
game_timer<@a> = $game_timer<@a>$ + 1;
|
525
|
+
|
526
|
+
if $game_timer<@a>$ >= 1200 {
|
527
|
+
game_timer<@a> = 0;
|
528
|
+
tellraw @s {"text":"Time's up! Final level: ","extra":[{"score":{"name":"@s","objective":"player_level"}}]};
|
529
|
+
}
|
530
|
+
}
|
531
|
+
|
532
|
+
on_tick "game:update_timer";
|
533
|
+
```
|
534
|
+
|
535
|
+
## Compilation Rules
|
536
|
+
|
537
|
+
### Variable Resolution
|
538
|
+
1. **Declaration**: Variables declare their storage scope when defined
|
539
|
+
2. **Reading**: `$variable<scope>$` gets converted to appropriate Minecraft scoreboard commands
|
540
|
+
3. **Writing**: `variable<scope>` specifies the target scope for assignments
|
541
|
+
4. **Access**: Variables can be accessed at any scope, regardless of where they were declared
|
542
|
+
|
543
|
+
### Function Compilation
|
544
|
+
1. **Exec Calls**: `exec function` becomes `execute as @s run function namespace:function`
|
545
|
+
2. **Exec Calls with Scope**: `exec function<@s>` becomes `execute as @s run function namespace:function`
|
546
|
+
3. **No Return Values**: Functions compile to a series of Minecraft commands
|
547
|
+
|
548
|
+
### Control Structure Compilation
|
549
|
+
1. **If Statements**: Comparisons compile to scoreboard comparisons. `!=` uses equality with inversion. Boolean expressions (`&&`, `||`, `!`) compile via temporary boolean scores and `execute` chaining.
|
550
|
+
2. **Else If Statements**: Handled as nested `if` with separate generated helper functions; chains are preserved.
|
551
|
+
3. **Else Blocks**: Compiled using inverted conditions with `execute unless` to run the else helper function.
|
552
|
+
4. **While Loops**: Generate recursive function calls that continue while the condition is true.
|
553
|
+
5. **Scheduled While Loops**: Generate a per-tick scheduled helper, tagging entities to iterate; schedule continues while the condition remains true.
|
554
|
+
6. **Nested Structures**: Automatically handle complex nested if/else and while loop combinations.
|
555
|
+
|
556
|
+
### Say Command Compilation
|
557
|
+
1. **Simple Text**: `say "message"` becomes `tellraw @a {"text":"message"}`
|
558
|
+
2. **With Variables**: `say "Score: $score<@s>$"` or `say "Score: $score$"` compiles to `tellraw` with a `score` component; `$var$` defaults to `<@s>`.
|
559
|
+
3. **Multiple Variables**: Complex variable substitutions are automatically formatted into proper JSON structure.
|
560
|
+
4. **Default Target**: All say commands target `@a` (all players) for maximum visibility.
|
561
|
+
|
562
|
+
### Tag Compilation
|
563
|
+
1. **Recipe Tags**: `tag recipe "name" "path"` generates appropriate tag files
|
564
|
+
2. **Loot Table Tags**: `tag loot_table "name" "path"` generates loot table tag files
|
565
|
+
3. **Advancement Tags**: `tag advancement "name" "path"` generates advancement tag files
|
566
|
+
4. **Item Modifier Tags**: `tag item_modifier "name" "path"` generates item modifier tag files
|
567
|
+
5. **Predicate Tags**: `tag predicate "name" "path"` generates predicate tag files
|
568
|
+
6. **Structure Tags**: `tag structure "name" "path"` generates structure tag files
|
569
|
+
|
570
|
+
### Error Handling
|
571
|
+
- **Undefined Variables**: Compilation error if variable not declared
|
572
|
+
- **Invalid Scopes**: Compilation error if scope selector is malformed
|
573
|
+
- **Missing Semicolons**: Compilation error for incomplete statements
|
574
|
+
- **Unterminated Blocks**: Compilation error for missing braces
|
575
|
+
- **Invalid Tag Paths**: Compilation error if tag file path is malformed
|
576
|
+
|
577
|
+
## Best Practices
|
578
|
+
|
579
|
+
1. **Always specify scopes explicitly** - Makes code clear and prevents bugs
|
580
|
+
2. **Use consistent syntax** - `$variable<scope>$` for reading, `variable<scope>` for writing
|
581
|
+
3. **Use meaningful variable names** - `player_score<@s>` is clearer than `score<@s>`
|
582
|
+
4. **Group related variables** - Keep variables with similar purposes together
|
583
|
+
5. **Comment complex scopes** - Explain non-standard selectors
|
584
|
+
6. **Avoid reserved names** - Don't use `load`, `tick`, or other Minecraft keywords
|
585
|
+
7. **Use consistent naming** - Pick a convention and stick to it
|
586
|
+
8. **Test scope combinations** - Verify that your scope logic works as expected
|
587
|
+
9. **Organize tag declarations** - Group related tags together at the top of files
|
588
|
+
10. **Use descriptive tag names** - Make tag names clear and meaningful
|
589
|
+
|
590
|
+
## Tokenization Specification
|
591
|
+
|
592
|
+
This section defines exactly how MDL source code is broken down into tokens. This specification is critical for maintaining consistency between the lexer, parser, and compiler.
|
593
|
+
|
594
|
+
### Core Token Types
|
595
|
+
|
596
|
+
#### **Keywords** (Reserved Words)
|
597
|
+
```
|
598
|
+
pack, namespace, function, var, num, if, else, while, scheduledwhile, on_load, on_tick, exec, tag
|
599
|
+
```
|
600
|
+
|
601
|
+
#### **Tag Types** (Resource Categories)
|
602
|
+
```
|
603
|
+
recipe, loot_table, advancement, item_modifier, predicate, structure
|
604
|
+
```
|
605
|
+
|
606
|
+
#### **Identifiers**
|
607
|
+
```
|
608
|
+
[a-zA-Z_][a-zA-Z0-9_]*
|
609
|
+
```
|
610
|
+
Examples: `player_score`, `game`, `start_game`, `_internal_var`
|
611
|
+
|
612
|
+
#### **Numbers**
|
613
|
+
```
|
614
|
+
[0-9]+(\.[0-9]+)?
|
615
|
+
```
|
616
|
+
Examples: `0`, `42`, `3.14`, `1000`
|
617
|
+
|
618
|
+
#### **Operators**
|
619
|
+
```
|
620
|
+
// Arithmetic
|
621
|
+
+ (PLUS), - (MINUS), * (MULTIPLY), / (DIVIDE)
|
622
|
+
|
623
|
+
// Comparison
|
624
|
+
== (EQUAL), != (NOT_EQUAL), > (GREATER), < (LESS), >= (GREATER_EQUAL), <= (LESS_EQUAL)
|
625
|
+
|
626
|
+
// Logical
|
627
|
+
&& (AND), || (OR), ! (NOT)
|
628
|
+
|
629
|
+
// Assignment
|
630
|
+
= (ASSIGN)
|
631
|
+
|
632
|
+
// Range
|
633
|
+
.. (RANGE)
|
634
|
+
|
635
|
+
// Execution
|
636
|
+
exec (EXEC)
|
637
|
+
```
|
638
|
+
|
639
|
+
#### **Delimiters**
|
640
|
+
```
|
641
|
+
; (SEMICOLON) - Statement terminator
|
642
|
+
, (COMMA) - Parameter separator
|
643
|
+
: (COLON) - Namespace separator
|
644
|
+
```
|
645
|
+
|
646
|
+
#### **Brackets and Braces**
|
647
|
+
```
|
648
|
+
( (LPAREN), ) (RPAREN) - Parentheses for expressions and function calls
|
649
|
+
{ (LBRACE), } (RBRACE) - Braces for code blocks
|
650
|
+
[ (LBRACKET), ] (RBRACKET) - Brackets for selectors and arrays
|
651
|
+
< (LANGLE), > (RANGLE) - Angle brackets for scope syntax
|
652
|
+
```
|
653
|
+
|
654
|
+
#### **Special Tokens**
|
655
|
+
```
|
656
|
+
$ (DOLLAR) - Variable substitution delimiter; line-start $... as MACRO_LINE
|
657
|
+
! (EXCLAMATION) - Used in $!raw markers
|
658
|
+
RAW_CONTENT - Entire content of a raw block
|
659
|
+
" (QUOTE) - String literal delimiter (supports both " and ' in lexer)
|
660
|
+
```
|
661
|
+
|
662
|
+
### Tag Declaration Tokenization
|
663
|
+
|
664
|
+
#### **Basic Tag Declaration**
|
665
|
+
```
|
666
|
+
tag recipe "RecipeName" "path/to/recipe.json";
|
667
|
+
```
|
668
|
+
Tokenized as:
|
669
|
+
1. `TAG` (`tag`)
|
670
|
+
2. `RECIPE` (`recipe`)
|
671
|
+
3. `QUOTE` (`"`)
|
672
|
+
4. `IDENTIFIER` (`RecipeName`)
|
673
|
+
5. `QUOTE` (`"`)
|
674
|
+
6. `QUOTE` (`"`)
|
675
|
+
7. `IDENTIFIER` (`path/to/recipe.json`)
|
676
|
+
8. `QUOTE` (`"`)
|
677
|
+
9. `SEMICOLON` (`;`)
|
678
|
+
|
679
|
+
#### **Tag Declaration with Complex Path**
|
680
|
+
```
|
681
|
+
tag loot_table "EpicLoot" "loot_tables/epic_loot.json";
|
682
|
+
```
|
683
|
+
Tokenized as:
|
684
|
+
1. `TAG` (`tag`)
|
685
|
+
2. `LOOT_TABLE` (`loot_table`)
|
686
|
+
3. `QUOTE` (`"`)
|
687
|
+
4. `IDENTIFIER` (`EpicLoot`)
|
688
|
+
5. `QUOTE` (`"`)
|
689
|
+
6. `QUOTE` (`"`)
|
690
|
+
7. `IDENTIFIER` (`loot_tables/epic_loot.json`)
|
691
|
+
8. `QUOTE` (`"`)
|
692
|
+
9. `SEMICOLON` (`;`)
|
693
|
+
```
|
694
|
+
|
695
|
+
### Scope Selector Tokenization
|
696
|
+
|
697
|
+
#### **Basic Selectors**
|
698
|
+
```
|
699
|
+
@s, @a, @p, @r
|
700
|
+
```
|
701
|
+
These are tokenized as single `IDENTIFIER` tokens.
|
702
|
+
|
703
|
+
#### **Complex Selectors**
|
704
|
+
```
|
705
|
+
@e[type=armor_stand,tag=mdl_global,limit=1]
|
706
|
+
```
|
707
|
+
This entire selector is tokenized as a single `IDENTIFIER` token.
|
708
|
+
|
709
|
+
#### **Scope Syntax**
|
710
|
+
```
|
711
|
+
<@s>, <@a[team=red]>, <global>
|
712
|
+
```
|
713
|
+
These are tokenized as:
|
714
|
+
1. `LANGLE` (`<`)
|
715
|
+
2. `IDENTIFIER` (the selector content)
|
716
|
+
3. `RANGLE` (`>`)
|
717
|
+
|
718
|
+
### Variable Substitution Tokenization
|
719
|
+
|
720
|
+
#### **Basic Substitution**
|
721
|
+
```
|
722
|
+
$player_score<@s>$
|
723
|
+
```
|
724
|
+
Tokenized as:
|
725
|
+
1. `DOLLAR` (`$`)
|
726
|
+
2. `IDENTIFIER` (`player_score`)
|
727
|
+
3. `LANGLE` (`<`)
|
728
|
+
4. `IDENTIFIER` (`@s`)
|
729
|
+
5. `RANGLE` (`>`)
|
730
|
+
6. `DOLLAR` (`$`)
|
731
|
+
|
732
|
+
#### **Shorthand (Default Scope)**
|
733
|
+
```
|
734
|
+
$player_score$
|
735
|
+
```
|
736
|
+
Tokenized as:
|
737
|
+
1. `DOLLAR` (`$`)
|
738
|
+
2. `IDENTIFIER` (`player_score`)
|
739
|
+
3. `DOLLAR` (`$`)
|
740
|
+
|
741
|
+
Note: When the scope is omitted, the parser defaults it to `<@s>` during AST construction.
|
742
|
+
|
743
|
+
#### **Complex Substitution**
|
744
|
+
```
|
745
|
+
$team_score<@a[team=red]>$
|
746
|
+
```
|
747
|
+
Tokenized as:
|
748
|
+
1. `DOLLAR` (`$`)
|
749
|
+
2. `IDENTIFIER` (`team_score`)
|
750
|
+
3. `LANGLE` (`<`)
|
751
|
+
4. `IDENTIFIER` (`@a[team=red]`)
|
752
|
+
5. `RANGLE` (`>`)
|
753
|
+
6. `DOLLAR` (`$`)
|
754
|
+
|
755
|
+
### Function Declaration Tokenization
|
756
|
+
|
757
|
+
#### **Basic Function**
|
758
|
+
```
|
759
|
+
function game:start_game {
|
760
|
+
```
|
761
|
+
Tokenized as:
|
762
|
+
1. `FUNCTION` (`function`)
|
763
|
+
2. `IDENTIFIER` (`game`)
|
764
|
+
3. `COLON` (`:`)
|
765
|
+
4. `IDENTIFIER` (`start_game`)
|
766
|
+
5. `LBRACE` (`{`)
|
767
|
+
|
768
|
+
#### **Function with Scope**
|
769
|
+
```
|
770
|
+
function game:reset_player {
|
771
|
+
```
|
772
|
+
Tokenized as:
|
773
|
+
1. `FUNCTION` (`function`)
|
774
|
+
2. `IDENTIFIER` (`game`)
|
775
|
+
3. `COLON` (`:`)
|
776
|
+
4. `IDENTIFIER` (`reset_player`)
|
777
|
+
5. `LANGLE` (`<`)
|
778
|
+
6. `IDENTIFIER` (`@s`)
|
779
|
+
7. `RANGLE` (`>`)
|
780
|
+
8. `LBRACE` (`{`)
|
781
|
+
|
782
|
+
### Function Call Tokenization
|
783
|
+
|
784
|
+
#### **Call with Scope**
|
785
|
+
```
|
786
|
+
exec game:reset_player<@s>;
|
787
|
+
```
|
788
|
+
Tokenized as:
|
789
|
+
1. `EXEC` (`exec`)
|
790
|
+
2. `IDENTIFIER` (`game`)
|
791
|
+
3. `COLON` (`:`)
|
792
|
+
4. `IDENTIFIER` (`reset_player`)
|
793
|
+
5. `LANGLE` (`<`)
|
794
|
+
6. `IDENTIFIER` (`@s`)
|
795
|
+
7. `RANGLE` (`>`)
|
796
|
+
8. `SEMICOLON` (`;`)
|
797
|
+
|
798
|
+
#### **Exec Call without Scope**
|
799
|
+
```
|
800
|
+
exec game:reset_player;
|
801
|
+
```
|
802
|
+
Tokenized as:
|
803
|
+
1. `EXEC` (`exec`)
|
804
|
+
2. `IDENTIFIER` (`game`)
|
805
|
+
3. `COLON` (`:`)
|
806
|
+
4. `IDENTIFIER` (`reset_player`)
|
807
|
+
5. `SEMICOLON` (`;`)
|
808
|
+
```
|
809
|
+
|
810
|
+
### Variable Declaration Tokenization
|
811
|
+
|
812
|
+
#### **Basic Declaration**
|
813
|
+
```
|
814
|
+
var num player_score<@s> = 0;
|
815
|
+
```
|
816
|
+
Tokenized as:
|
817
|
+
1. `VAR` (`var`)
|
818
|
+
2. `NUM` (`num`)
|
819
|
+
3. `IDENTIFIER` (`player_score`)
|
820
|
+
4. `LANGLE` (`<`)
|
821
|
+
5. `IDENTIFIER` (`@s`)
|
822
|
+
6. `RANGLE` (`>`)
|
823
|
+
7. `ASSIGN` (`=`)
|
824
|
+
8. `NUMBER` (`0`)
|
825
|
+
9. `SEMICOLON` (`;`)
|
826
|
+
|
827
|
+
### Variable Assignment Tokenization
|
828
|
+
|
829
|
+
#### **Simple Assignment**
|
830
|
+
```
|
831
|
+
player_score<@s> = 42;
|
832
|
+
```
|
833
|
+
Tokenized as:
|
834
|
+
1. `IDENTIFIER` (`player_score`)
|
835
|
+
2. `LANGLE` (`<`)
|
836
|
+
3. `IDENTIFIER` (`@s`)
|
837
|
+
4. `RANGLE` (`>`)
|
838
|
+
5. `ASSIGN` (`=`)
|
839
|
+
6. `NUMBER` (`42`)
|
840
|
+
7. `SEMICOLON` (`;`)
|
841
|
+
|
842
|
+
#### **Expression Assignment**
|
843
|
+
```
|
844
|
+
player_score<@s> = $player_score<@s>$ + 1;
|
845
|
+
```
|
846
|
+
Tokenized as:
|
847
|
+
1. `IDENTIFIER` (`player_score`)
|
848
|
+
2. `LANGLE` (`<`)
|
849
|
+
3. `IDENTIFIER` (`@s`)
|
850
|
+
4. `RANGLE` (`>`)
|
851
|
+
5. `ASSIGN` (`=`)
|
852
|
+
6. `DOLLAR` (`$`)
|
853
|
+
7. `IDENTIFIER` (`player_score`)
|
854
|
+
8. `LANGLE` (`<`)
|
855
|
+
9. `IDENTIFIER` (`@s`)
|
856
|
+
10. `RANGLE` (`>`)
|
857
|
+
11. `DOLLAR` (`$`)
|
858
|
+
12. `PLUS` (`+`)
|
859
|
+
13. `NUMBER` (`1`)
|
860
|
+
14. `SEMICOLON` (`;`)
|
861
|
+
|
862
|
+
### Control Structure Tokenization
|
863
|
+
|
864
|
+
#### **If Statement**
|
865
|
+
```
|
866
|
+
if $player_score<@s>$ > 10 {
|
867
|
+
```
|
868
|
+
Tokenized as:
|
869
|
+
1. `IF` (`if`)
|
870
|
+
2. `DOLLAR` (`$`)
|
871
|
+
3. `IDENTIFIER` (`player_score`)
|
872
|
+
4. `LANGLE` (`<`)
|
873
|
+
5. `IDENTIFIER` (`@s`)
|
874
|
+
6. `RANGLE` (`>`)
|
875
|
+
7. `DOLLAR` (`$`)
|
876
|
+
8. `GREATER` (`>`)
|
877
|
+
9. `NUMBER` (`10`)
|
878
|
+
10. `LBRACE` (`{`)
|
879
|
+
|
880
|
+
#### **While Loop**
|
881
|
+
```
|
882
|
+
while $counter<@s>$ > 0 {
|
883
|
+
```
|
884
|
+
Tokenized as:
|
885
|
+
1. `WHILE` (`while`)
|
886
|
+
2. `DOLLAR` (`$`)
|
887
|
+
3. `IDENTIFIER` (`counter`)
|
888
|
+
4. `LANGLE` (`<`)
|
889
|
+
5. `IDENTIFIER` (`@s`)
|
890
|
+
6. `RANGLE` (`>`)
|
891
|
+
7. `DOLLAR` (`$`)
|
892
|
+
8. `GREATER` (`>`)
|
893
|
+
9. `NUMBER` (`0`)
|
894
|
+
10. `LBRACE` (`{`)
|
895
|
+
|
896
|
+
### Raw Block Tokenization
|
897
|
+
|
898
|
+
#### **Raw Block Start**
|
899
|
+
```
|
900
|
+
$!raw
|
901
|
+
```
|
902
|
+
Tokenized as:
|
903
|
+
1. `DOLLAR` (`$`)
|
904
|
+
2. `EXCLAMATION` (`!`)
|
905
|
+
3. `IDENTIFIER` (`raw`)
|
906
|
+
|
907
|
+
#### **Raw Block End**
|
908
|
+
```
|
909
|
+
raw!$
|
910
|
+
```
|
911
|
+
Tokenized as:
|
912
|
+
1. `IDENTIFIER` (`raw`)
|
913
|
+
2. `EXCLAMATION` (`!`)
|
914
|
+
3. `DOLLAR` (`$`)
|
915
|
+
|
916
|
+
### Whitespace and Comments
|
917
|
+
|
918
|
+
#### **Whitespace**
|
919
|
+
- Spaces, tabs, and newlines are ignored during tokenization
|
920
|
+
- They serve only to separate tokens
|
921
|
+
- Multiple consecutive whitespace characters are treated as a single separator
|
922
|
+
|
923
|
+
#### **Comments**
|
924
|
+
```
|
925
|
+
// Single line comment
|
926
|
+
/* Multi-line comment */
|
927
|
+
```
|
928
|
+
Comments are completely ignored during tokenization and do not generate any tokens.
|
929
|
+
|
930
|
+
**Comment Rules:**
|
931
|
+
- Single-line comments start with `//` and continue to the end of the line
|
932
|
+
- Multi-line comments start with `/*` and end with `*/`
|
933
|
+
- Comments can appear anywhere in the code
|
934
|
+
- Comments are stripped out before processing - they don't affect the generated `.mcfunction` files
|
935
|
+
|
936
|
+
### Tokenization Rules
|
937
|
+
|
938
|
+
1. **Longest Match**: Always consume the longest possible token (e.g., `>=` not `>` then `=`)
|
939
|
+
2. **No Ambiguity**: Each character sequence maps to exactly one token type
|
940
|
+
3. **Scope Priority**: Scope selectors are always tokenized as complete `IDENTIFIER` tokens
|
941
|
+
4. **No Context**: Tokenization is context-free - the same character sequence always produces the same tokens
|
942
|
+
5. **Error Handling**: Invalid characters or unterminated sequences generate appropriate error tokens
|
943
|
+
6. **String Handling**: Quoted strings are tokenized as complete units with their delimiters
|
944
|
+
|
945
|
+
### Example Complete Tokenization
|
946
|
+
|
947
|
+
```mdl
|
948
|
+
tag recipe "diamond_sword" "recipes/diamond_sword.json";
|
949
|
+
var num player_score<@s> = 0;
|
950
|
+
```
|
951
|
+
|
952
|
+
**Tokens Generated:**
|
953
|
+
1. `TAG` (`tag`)
|
954
|
+
2. `RECIPE` (`recipe`)
|
955
|
+
3. `QUOTE` (`"`)
|
956
|
+
4. `IDENTIFIER` (`diamond_sword`)
|
957
|
+
5. `QUOTE` (`"`)
|
958
|
+
6. `QUOTE` (`"`)
|
959
|
+
7. `IDENTIFIER` (`recipes/diamond_sword.json`)
|
960
|
+
8. `QUOTE` (`"`)
|
961
|
+
9. `SEMICOLON` (`;`)
|
962
|
+
10. `VAR` (`var`)
|
963
|
+
11. `NUM` (`num`)
|
964
|
+
12. `IDENTIFIER` (`player_score`)
|
965
|
+
13. `LANGLE` (`<`)
|
966
|
+
14. `IDENTIFIER` (`@s`)
|
967
|
+
15. `RANGLE` (`>`)
|
968
|
+
16. `ASSIGN` (`=`)
|
969
|
+
17. `NUMBER` (`0`)
|
970
|
+
18. `SEMICOLON` (`;`)
|
971
|
+
19. `EOF`
|
972
|
+
|
973
|
+
This tokenization specification ensures that the lexer, parser, and compiler all work with the same understanding of how MDL source code is structured.
|
974
|
+
|
975
|
+
## Edge Cases and Error Handling
|
976
|
+
|
977
|
+
### Common Error Scenarios
|
978
|
+
|
979
|
+
#### **Unterminated Scope Selectors**
|
980
|
+
```mdl
|
981
|
+
// ❌ Error: Missing closing >
|
982
|
+
var num score<@s = 0;
|
983
|
+
|
984
|
+
// ✅ Correct
|
985
|
+
var num score<@s> = 0;
|
986
|
+
```
|
987
|
+
|
988
|
+
#### **Invalid Scope Selectors**
|
989
|
+
```mdl
|
990
|
+
// ❌ Error: Invalid selector syntax
|
991
|
+
var num score<@invalid[type=armor_stand]> = 0;
|
992
|
+
|
993
|
+
// ✅ Correct
|
994
|
+
var num score<@e[type=armor_stand,tag=mdl_global,limit=1]> = 0;
|
995
|
+
```
|
996
|
+
|
997
|
+
#### **Missing Semicolons**
|
998
|
+
```mdl
|
999
|
+
// ❌ Error: Missing semicolon
|
1000
|
+
var num score<@s> = 0
|
1001
|
+
player_score<@s> = 5
|
1002
|
+
|
1003
|
+
// ✅ Correct
|
1004
|
+
var num score<@s> = 0;
|
1005
|
+
player_score<@s> = 5;
|
1006
|
+
```
|
1007
|
+
|
1008
|
+
#### **Unterminated Blocks**
|
1009
|
+
```mdl
|
1010
|
+
// ❌ Error: Missing closing brace
|
1011
|
+
function game:test {
|
1012
|
+
player_score<@s> = 0;
|
1013
|
+
// Missing }
|
1014
|
+
|
1015
|
+
// ✅ Correct
|
1016
|
+
function game:test {
|
1017
|
+
player_score<@s> = 0;
|
1018
|
+
}
|
1019
|
+
```
|
1020
|
+
|
1021
|
+
#### **Invalid Variable References**
|
1022
|
+
```mdl
|
1023
|
+
// ❌ Error: Variable not declared
|
1024
|
+
player_score<@s> = 0;
|
1025
|
+
score<@s> = 5; // 'score' was never declared
|
1026
|
+
|
1027
|
+
// ✅ Correct
|
1028
|
+
var num score<@s> = 0;
|
1029
|
+
player_score<@s> = 0;
|
1030
|
+
score<@s> = 5;
|
1031
|
+
```
|
1032
|
+
|
1033
|
+
#### **Invalid Tag Declarations**
|
1034
|
+
```mdl
|
1035
|
+
// ❌ Error: Missing quotes
|
1036
|
+
tag recipe RecipeName "path/to/recipe.json";
|
1037
|
+
|
1038
|
+
// ❌ Error: Missing semicolon
|
1039
|
+
tag recipe "RecipeName" "path/to/recipe.json"
|
1040
|
+
|
1041
|
+
// ✅ Correct
|
1042
|
+
tag recipe "RecipeName" "path/to/recipe.json";
|
1043
|
+
```
|
1044
|
+
|
1045
|
+
### Complex Edge Cases
|
1046
|
+
|
1047
|
+
#### **Nested Scope Selectors in Raw Blocks**
|
1048
|
+
```mdl
|
1049
|
+
// This is valid - raw blocks pass through unchanged
|
1050
|
+
$!raw
|
1051
|
+
execute if score @s player_score<@s> matches 10.. run function game:celebrate
|
1052
|
+
raw!$
|
1053
|
+
```
|
1054
|
+
|
1055
|
+
#### **Scope Selectors with Special Characters**
|
1056
|
+
```mdl
|
1057
|
+
// Valid - selector with complex parameters
|
1058
|
+
var num data<@e[type=armor_stand,tag=mdl_global,limit=1,nbt={CustomName:'{"text":"Server"}'}]> = 0;
|
1059
|
+
```
|
1060
|
+
|
1061
|
+
#### **Variable Names with Underscores**
|
1062
|
+
```mdl
|
1063
|
+
// Valid - underscores are allowed in variable names
|
1064
|
+
var num player_score_red_team<@a[team=red]> = 0;
|
1065
|
+
var num _internal_counter<@s> = 0;
|
1066
|
+
```
|
1067
|
+
|
1068
|
+
#### **Function Names with Numbers**
|
1069
|
+
```mdl
|
1070
|
+
// Valid - numbers are allowed in function names
|
1071
|
+
function game:level_1_complete {
|
1072
|
+
player_score<@s> = player_score<@s> + 100;
|
1073
|
+
}
|
1074
|
+
```
|
1075
|
+
|
1076
|
+
#### **Tag Paths with Special Characters**
|
1077
|
+
```mdl
|
1078
|
+
// Valid - paths can contain various characters
|
1079
|
+
tag recipe "complex_recipe" "recipes/complex/recipe_v1.2.json";
|
1080
|
+
tag loot_table "special_loot" "loot_tables/special/loot_#1.json";
|
1081
|
+
```
|
1082
|
+
|
1083
|
+
### Error Recovery
|
1084
|
+
|
1085
|
+
The MDL compiler attempts to provide helpful error messages:
|
1086
|
+
|
1087
|
+
1. **Line and Column Information** - Shows exactly where the error occurred
|
1088
|
+
2. **Context** - Displays the problematic line with surrounding context
|
1089
|
+
3. **Suggestions** - Provides specific guidance on how to fix the error
|
1090
|
+
4. **Error Categories** - Groups errors by type (syntax, scope, undefined variables, invalid tags, etc.)
|
1091
|
+
|
1092
|
+
### Performance Considerations
|
1093
|
+
|
1094
|
+
- **Large Selectors**: Very long scope selectors may impact compilation time
|
1095
|
+
- **Deep Nesting**: Excessive nesting of control structures may affect parsing performance
|
1096
|
+
- **Raw Block Size**: Large raw blocks are processed efficiently as they're copied without parsing
|
1097
|
+
- **Tag Processing**: Tag declarations are processed efficiently as they're simple string operations
|
1098
|
+
|
1099
|
+
## Abstract Syntax Tree (AST) Implementation
|
1100
|
+
|
1101
|
+
The MDL language is implemented using a comprehensive Abstract Syntax Tree (AST) system that represents the parsed code structure. This section explains how the AST works and how it represents all language constructs.
|
1102
|
+
|
1103
|
+
### AST Node Hierarchy
|
1104
|
+
|
1105
|
+
The AST is built using a hierarchy of node classes, each representing a specific language construct:
|
1106
|
+
|
1107
|
+
#### **Root Node: Program**
|
1108
|
+
```python
|
1109
|
+
class Program:
|
1110
|
+
pack: Optional[PackDeclaration] # Pack metadata
|
1111
|
+
namespace: Optional[NamespaceDeclaration] # Default namespace
|
1112
|
+
tags: List[TagDeclaration] # Resource tag declarations
|
1113
|
+
variables: List[VariableDeclaration] # Variable declarations
|
1114
|
+
functions: List[FunctionDeclaration] # Function definitions
|
1115
|
+
hooks: List[HookDeclaration] # Event hooks (on_load, on_tick)
|
1116
|
+
statements: List[ASTNode] # Top-level statements
|
1117
|
+
```
|
1118
|
+
|
1119
|
+
The `Program` node serves as the root of the AST, containing all top-level declarations and statements. This structure allows the compiler to:
|
1120
|
+
- Generate the `pack.mcmeta` file from pack declarations
|
1121
|
+
- Create the proper directory structure based on namespace
|
1122
|
+
- Process all tag references for resource generation
|
1123
|
+
- Manage variable scoping across the entire program
|
1124
|
+
- Generate function files with proper namespacing
|
1125
|
+
- Set up event hooks for automatic execution
|
1126
|
+
|
1127
|
+
#### **Declaration Nodes**
|
1128
|
+
|
1129
|
+
**PackDeclaration**
|
1130
|
+
```python
|
1131
|
+
class PackDeclaration:
|
1132
|
+
name: str # Pack name (e.g., "MyGame")
|
1133
|
+
description: str # Pack description
|
1134
|
+
pack_format: int # Minecraft pack format version
|
1135
|
+
```
|
1136
|
+
|
1137
|
+
**NamespaceDeclaration**
|
1138
|
+
```python
|
1139
|
+
class NamespaceDeclaration:
|
1140
|
+
name: str # Namespace name (e.g., "game")
|
1141
|
+
```
|
1142
|
+
|
1143
|
+
**TagDeclaration**
|
1144
|
+
```python
|
1145
|
+
class TagDeclaration:
|
1146
|
+
tag_type: str # Resource type (recipe, loot_table, etc.)
|
1147
|
+
name: str # Tag name
|
1148
|
+
file_path: str # Path to the JSON file
|
1149
|
+
```
|
1150
|
+
|
1151
|
+
**VariableDeclaration**
|
1152
|
+
```python
|
1153
|
+
class VariableDeclaration:
|
1154
|
+
var_type: str # Variable type (currently only "num")
|
1155
|
+
name: str # Variable name
|
1156
|
+
scope: str # Scope selector (e.g., "<@s>", "<@a[team=red]>")
|
1157
|
+
initial_value: Any # Initial value expression
|
1158
|
+
```
|
1159
|
+
|
1160
|
+
**FunctionDeclaration**
|
1161
|
+
```python
|
1162
|
+
class FunctionDeclaration:
|
1163
|
+
namespace: str # Function namespace
|
1164
|
+
name: str # Function name
|
1165
|
+
scope: Optional[str] # Optional scope for function execution
|
1166
|
+
body: List[ASTNode] # Function body statements
|
1167
|
+
```
|
1168
|
+
|
1169
|
+
**HookDeclaration**
|
1170
|
+
```python
|
1171
|
+
class HookDeclaration:
|
1172
|
+
hook_type: str # Hook type ("on_load" or "on_tick")
|
1173
|
+
namespace: str # Function namespace to call
|
1174
|
+
name: str # Function name to call
|
1175
|
+
scope: Optional[str] # Optional scope for hook execution
|
1176
|
+
```
|
1177
|
+
|
1178
|
+
#### **Statement Nodes**
|
1179
|
+
|
1180
|
+
**VariableAssignment**
|
1181
|
+
```python
|
1182
|
+
class VariableAssignment:
|
1183
|
+
name: str # Variable name
|
1184
|
+
scope: str # Scope selector
|
1185
|
+
value: Any # Value expression
|
1186
|
+
```
|
1187
|
+
|
1188
|
+
**VariableSubstitution**
|
1189
|
+
```python
|
1190
|
+
class VariableSubstitution:
|
1191
|
+
name: str # Variable name
|
1192
|
+
scope: str # Scope selector
|
1193
|
+
```
|
1194
|
+
|
1195
|
+
**FunctionCall**
|
1196
|
+
```python
|
1197
|
+
class FunctionCall:
|
1198
|
+
namespace: str # Function namespace
|
1199
|
+
name: str # Function name
|
1200
|
+
scope: Optional[str] # Optional scope
|
1201
|
+
```
|
1202
|
+
|
1203
|
+
**Control Structures**
|
1204
|
+
```python
|
1205
|
+
class IfStatement:
|
1206
|
+
condition: Any # Condition expression
|
1207
|
+
then_body: List[ASTNode] # Then block statements
|
1208
|
+
else_body: Optional[List[ASTNode]] # Optional else block
|
1209
|
+
|
1210
|
+
class WhileLoop:
|
1211
|
+
condition: Any # Loop condition
|
1212
|
+
body: List[ASTNode] # Loop body statements
|
1213
|
+
```
|
1214
|
+
|
1215
|
+
**Commands**
|
1216
|
+
```python
|
1217
|
+
class SayCommand:
|
1218
|
+
message: str # Message text with variable placeholders
|
1219
|
+
variables: List[VariableSubstitution] # Extracted variables
|
1220
|
+
|
1221
|
+
class RawBlock:
|
1222
|
+
content: str # Raw content (passed through unchanged)
|
1223
|
+
```
|
1224
|
+
|
1225
|
+
#### **Expression Nodes**
|
1226
|
+
|
1227
|
+
**BinaryExpression**
|
1228
|
+
```python
|
1229
|
+
class BinaryExpression:
|
1230
|
+
left: Any # Left operand
|
1231
|
+
operator: str # Operator (+, -, *, /, >, <, >=, <=, ==, !=)
|
1232
|
+
right: Any # Right operand
|
1233
|
+
```
|
1234
|
+
|
1235
|
+
**LiteralExpression**
|
1236
|
+
```python
|
1237
|
+
class LiteralExpression:
|
1238
|
+
value: Any # Literal value
|
1239
|
+
type: str # Value type ("number", "string", "identifier")
|
1240
|
+
```
|
1241
|
+
|
1242
|
+
**ParenthesizedExpression**
|
1243
|
+
```python
|
1244
|
+
class ParenthesizedExpression:
|
1245
|
+
expression: Any # Expression inside parentheses
|
1246
|
+
```
|
1247
|
+
|
1248
|
+
### AST Construction Process
|
1249
|
+
|
1250
|
+
The AST is constructed through a multi-stage process:
|
1251
|
+
|
1252
|
+
1. **Lexical Analysis**: Source code is converted to tokens
|
1253
|
+
2. **Parsing**: Tokens are parsed into AST nodes
|
1254
|
+
3. **Validation**: AST structure is validated for correctness
|
1255
|
+
4. **Compilation**: AST is traversed to generate output
|
1256
|
+
|
1257
|
+
#### **Lexical Analysis (Tokenization)**
|
1258
|
+
|
1259
|
+
The lexer converts source code into a stream of tokens:
|
1260
|
+
|
1261
|
+
```python
|
1262
|
+
# Source: var num score<@s> = 0;
|
1263
|
+
# Tokens: [VAR, NUM, IDENTIFIER('score'), LESS, IDENTIFIER('@s'),
|
1264
|
+
# GREATER, ASSIGN, NUMBER('0'), SEMICOLON]
|
1265
|
+
```
|
1266
|
+
|
1267
|
+
**Token Types**
|
1268
|
+
- **Keywords**: `PACK`, `NAMESPACE`, `FUNCTION`, `VAR`, `IF`, `WHILE`, etc.
|
1269
|
+
- **Operators**: `PLUS`, `MINUS`, `MULTIPLY`, `DIVIDE`, `ASSIGN`, `GREATER`, `LESS`, etc.
|
1270
|
+
- **Delimiters**: `SEMICOLON`, `COMMA`, `COLON`, `LPAREN`, `RPAREN`, etc.
|
1271
|
+
- **Literals**: `IDENTIFIER`, `NUMBER`, `QUOTE`
|
1272
|
+
- **Special**: `DOLLAR`, `EXCLAMATION`, `RAW_CONTENT`
|
1273
|
+
|
1274
|
+
#### **Parsing Strategy**
|
1275
|
+
|
1276
|
+
The parser uses a recursive descent approach with operator precedence:
|
1277
|
+
|
1278
|
+
```python
|
1279
|
+
def _parse_expression(self) -> Any:
|
1280
|
+
"""Parse expressions with proper operator precedence."""
|
1281
|
+
return self._parse_comparison()
|
1282
|
+
|
1283
|
+
def _parse_comparison(self) -> Any:
|
1284
|
+
"""Parse comparison expressions (>, <, >=, <=, ==, !=)."""
|
1285
|
+
expr = self._parse_term()
|
1286
|
+
while self._peek().type in [GREATER, LESS, GREATER_EQUAL, LESS_EQUAL, EQUAL, NOT_EQUAL]:
|
1287
|
+
operator = self._advance().type
|
1288
|
+
right = self._parse_term()
|
1289
|
+
expr = BinaryExpression(left=expr, operator=operator, right=right)
|
1290
|
+
return expr
|
1291
|
+
```
|
1292
|
+
|
1293
|
+
**Operator Precedence** (highest to lowest):
|
1294
|
+
1. **Primary**: Variables, literals, parenthesized expressions
|
1295
|
+
2. **Factors**: Multiplication, division
|
1296
|
+
3. **Terms**: Addition, subtraction
|
1297
|
+
4. **Comparisons**: Greater, less, equal, not equal
|
1298
|
+
|
1299
|
+
### Logical Operators - Compilation Notes
|
1300
|
+
|
1301
|
+
- Logical expressions compile into temporary boolean scoreboard values (1 true, 0 false) checked via `execute if/unless`.
|
1302
|
+
- `&&` is compiled as a chain of `execute if` conditions; `||` sets the result true if either operand is true.
|
1303
|
+
- `!` negates the entire operand. For comparisons like `!$a<@s>$ > 0`, the comparison is evaluated first, then negated.
|
1304
|
+
- `!=` is compiled using equality with inversion (`unless score ... = ...`) because Minecraft lacks a direct not-equal comparator.
|
1305
|
+
|
1306
|
+
### Integer-Only Arithmetic and Literal Handling
|
1307
|
+
|
1308
|
+
- Scoreboard arithmetic is integer-only. MDL normalizes integer-like literals: `2.0` -> `2`. Non-integer literals (e.g., `2.5`) cause a compile-time error when used in scoreboard math.
|
1309
|
+
- Literal addition/subtraction uses `scoreboard players add/remove`; elides `+0`/`-0`.
|
1310
|
+
- Literal multiplication/division uses `scoreboard players multiply/divide`; elides `*1`/`/1`. `*0` sets the temp to zero. Division by zero is a compile-time error.
|
1311
|
+
- Mixed expressions are lowered via temporary scores to preserve precedence. Score-to-score operations use `scoreboard players operation`.
|
1312
|
+
|
1313
|
+
### AST Traversal and Code Generation
|
1314
|
+
|
1315
|
+
The AST is designed to support efficient code generation:
|
1316
|
+
|
1317
|
+
#### **Visitor Pattern Support**
|
1318
|
+
```python
|
1319
|
+
class ASTVisitor:
|
1320
|
+
def visit_program(self, node: Program): pass
|
1321
|
+
def visit_variable_declaration(self, node: VariableDeclaration): pass
|
1322
|
+
def visit_function_declaration(self, node: FunctionDeclaration): pass
|
1323
|
+
# ... other visit methods
|
1324
|
+
```
|
1325
|
+
|
1326
|
+
#### **Code Generation Strategy**
|
1327
|
+
1. **Pack Generation**: Create `pack.mcmeta` from pack declarations
|
1328
|
+
2. **Namespace Setup**: Establish directory structure
|
1329
|
+
3. **Tag Processing**: Generate resource references
|
1330
|
+
4. **Variable Management**: Set up scoreboard objectives
|
1331
|
+
5. **Function Generation**: Create `.mcfunction` files
|
1332
|
+
6. **Hook Integration**: Set up automatic execution
|
1333
|
+
|
1334
|
+
### Error Handling and Recovery
|
1335
|
+
|
1336
|
+
The AST system provides comprehensive error handling:
|
1337
|
+
|
1338
|
+
#### **Parser Error Context**
|
1339
|
+
```python
|
1340
|
+
class MDLParserError:
|
1341
|
+
message: str # Error description
|
1342
|
+
file_path: str # Source file path
|
1343
|
+
line: int # Error line number
|
1344
|
+
column: int # Error column number
|
1345
|
+
line_content: str # Problematic line content
|
1346
|
+
suggestion: str # How to fix the error
|
1347
|
+
```
|
1348
|
+
|
1349
|
+
#### **Error Recovery Strategies**
|
1350
|
+
1. **Graceful Degradation**: Continue parsing when possible
|
1351
|
+
2. **Context Preservation**: Maintain line/column information
|
1352
|
+
3. **Helpful Messages**: Provide specific fix suggestions
|
1353
|
+
4. **Error Aggregation**: Collect multiple errors when possible
|
1354
|
+
|
1355
|
+
### Extensibility Features
|
1356
|
+
|
1357
|
+
The AST system is designed for easy extension:
|
1358
|
+
|
1359
|
+
#### **Adding New Node Types**
|
1360
|
+
```python
|
1361
|
+
class NewNode(ASTNode):
|
1362
|
+
def __init__(self, new_field: str):
|
1363
|
+
self.new_field = new_field
|
1364
|
+
```
|
1365
|
+
|
1366
|
+
#### **Adding New Parsers**
|
1367
|
+
```python
|
1368
|
+
def _parse_new_construct(self) -> NewNode:
|
1369
|
+
# Parse new language construct
|
1370
|
+
pass
|
1371
|
+
```
|
1372
|
+
|
1373
|
+
#### **Adding New Token Types**
|
1374
|
+
```python
|
1375
|
+
class TokenType:
|
1376
|
+
# ... existing types ...
|
1377
|
+
NEW_TYPE = "NEW_TYPE"
|
1378
|
+
```
|
1379
|
+
|
1380
|
+
## Parsing System Implementation
|
1381
|
+
|
1382
|
+
The MDL parser implements a robust, extensible parsing system that handles all language constructs defined in the specification. This section explains how the parsing works and how it processes the language.
|
1383
|
+
|
1384
|
+
### Parser Architecture
|
1385
|
+
|
1386
|
+
The parser uses a **recursive descent** approach with **lookahead** capabilities:
|
1387
|
+
|
1388
|
+
```python
|
1389
|
+
class MDLParser:
|
1390
|
+
def __init__(self, source_file: str = None):
|
1391
|
+
self.source_file = source_file
|
1392
|
+
self.tokens: List[Token] = []
|
1393
|
+
self.current = 0
|
1394
|
+
self.current_namespace = "mdl"
|
1395
|
+
```
|
1396
|
+
|
1397
|
+
#### **Core Parsing Methods**
|
1398
|
+
|
1399
|
+
**Program Parsing**
|
1400
|
+
```python
|
1401
|
+
def _parse_program(self) -> Program:
|
1402
|
+
"""Parse the complete program structure."""
|
1403
|
+
pack = None
|
1404
|
+
namespace = None
|
1405
|
+
tags = []
|
1406
|
+
variables = []
|
1407
|
+
functions = []
|
1408
|
+
hooks = []
|
1409
|
+
statements = []
|
1410
|
+
|
1411
|
+
while not self._is_at_end():
|
1412
|
+
# Parse top-level constructs based on token type
|
1413
|
+
if self._peek().type == TokenType.PACK:
|
1414
|
+
pack = self._parse_pack_declaration()
|
1415
|
+
elif self._peek().type == TokenType.NAMESPACE:
|
1416
|
+
namespace = self._parse_namespace_declaration()
|
1417
|
+
# ... continue with other constructs
|
1418
|
+
```
|
1419
|
+
|
1420
|
+
**Declaration Parsing**
|
1421
|
+
```python
|
1422
|
+
def _parse_pack_declaration(self) -> PackDeclaration:
|
1423
|
+
"""Parse: pack "name" "description" format;"""
|
1424
|
+
self._expect(TokenType.PACK, "Expected 'pack' keyword")
|
1425
|
+
self._expect(TokenType.QUOTE, "Expected opening quote for pack name")
|
1426
|
+
name = self._expect_identifier("Expected pack name")
|
1427
|
+
# ... continue parsing
|
1428
|
+
```
|
1429
|
+
|
1430
|
+
**Statement Parsing**
|
1431
|
+
```python
|
1432
|
+
def _parse_if_statement(self) -> IfStatement:
|
1433
|
+
"""Parse: if condition { then_body } else { else_body }"""
|
1434
|
+
self._expect(TokenType.IF, "Expected 'if' keyword")
|
1435
|
+
condition = self._parse_expression()
|
1436
|
+
self._expect(TokenType.LBRACE, "Expected '{' to start if body")
|
1437
|
+
then_body = self._parse_block()
|
1438
|
+
# ... handle optional else clause
|
1439
|
+
```
|
1440
|
+
|
1441
|
+
### Expression Parsing with Operator Precedence
|
1442
|
+
|
1443
|
+
The parser implements a **Pratt parser** approach for expressions:
|
1444
|
+
|
1445
|
+
```python
|
1446
|
+
def _parse_expression(self) -> Any:
|
1447
|
+
"""Entry point for expression parsing."""
|
1448
|
+
return self._parse_comparison()
|
1449
|
+
|
1450
|
+
def _parse_comparison(self) -> Any:
|
1451
|
+
"""Parse comparison expressions with left associativity."""
|
1452
|
+
expr = self._parse_term()
|
1453
|
+
|
1454
|
+
while not self._is_at_end() and self._peek().type in [
|
1455
|
+
TokenType.GREATER, TokenType.LESS, TokenType.GREATER_EQUAL,
|
1456
|
+
TokenType.LESS_EQUAL, TokenType.EQUAL, TokenType.NOT_EQUAL
|
1457
|
+
]:
|
1458
|
+
operator = self._peek().type
|
1459
|
+
self._advance()
|
1460
|
+
right = self._parse_term()
|
1461
|
+
expr = BinaryExpression(left=expr, operator=operator, right=right)
|
1462
|
+
|
1463
|
+
return expr
|
1464
|
+
```
|
1465
|
+
|
1466
|
+
**Precedence Levels**:
|
1467
|
+
1. **Primary**: Variables, literals, parentheses
|
1468
|
+
2. **Factors**: `*`, `/`
|
1469
|
+
3. **Terms**: `+`, `-`
|
1470
|
+
4. **Comparisons**: `>`, `<`, `>=`, `<=`, `==`, `!=`
|
1471
|
+
|
1472
|
+
### Scope Selector Parsing
|
1473
|
+
|
1474
|
+
Scope selectors are parsed differently based on context:
|
1475
|
+
|
1476
|
+
```python
|
1477
|
+
def _parse_scope_selector(self) -> str:
|
1478
|
+
"""Parse scope selector: <@s>, <@a[team=red]>, etc."""
|
1479
|
+
self._expect(TokenType.LESS, "Expected '<' for scope selector")
|
1480
|
+
|
1481
|
+
selector_content = ""
|
1482
|
+
while not self._is_at_end() and self._peek().type != TokenType.GREATER:
|
1483
|
+
selector_content += self._peek().value
|
1484
|
+
self._advance()
|
1485
|
+
|
1486
|
+
self._expect(TokenType.GREATER, "Expected '>' to close scope selector")
|
1487
|
+
return f"<{selector_content}>"
|
1488
|
+
```
|
1489
|
+
|
1490
|
+
**Context-Sensitive Parsing**:
|
1491
|
+
- **Variable Declarations**: Use `LESS`/`GREATER` tokens
|
1492
|
+
- **Variable Substitutions**: Use `LANGLE`/`RANGLE` tokens
|
1493
|
+
- **Function Parameters**: Use `LESS`/`GREATER` tokens
|
1494
|
+
|
1495
|
+
### Raw Block Processing
|
1496
|
+
|
1497
|
+
Raw blocks are handled specially to preserve exact content:
|
1498
|
+
|
1499
|
+
```python
|
1500
|
+
def _parse_raw_block(self) -> RawBlock:
|
1501
|
+
"""Parse: $!raw ... raw!$"""
|
1502
|
+
# Consume $!raw
|
1503
|
+
self._expect(TokenType.DOLLAR, "Expected '$' to start raw block")
|
1504
|
+
self._expect(TokenType.EXCLAMATION, "Expected '!' after '$' in raw block")
|
1505
|
+
self._expect(TokenType.IDENTIFIER, "Expected 'raw' keyword")
|
1506
|
+
|
1507
|
+
# Look for RAW_CONTENT token (generated by lexer)
|
1508
|
+
if self._peek().type == TokenType.RAW_CONTENT:
|
1509
|
+
content = self._peek().value
|
1510
|
+
self._advance()
|
1511
|
+
else:
|
1512
|
+
content = ""
|
1513
|
+
|
1514
|
+
# Consume raw!$ end marker
|
1515
|
+
self._expect(TokenType.IDENTIFIER, "Expected 'raw' to end raw block")
|
1516
|
+
self._expect(TokenType.EXCLAMATION, "Expected '!' to end raw block")
|
1517
|
+
self._expect(TokenType.DOLLAR, "Expected '$' to end raw block")
|
1518
|
+
|
1519
|
+
return RawBlock(content=content)
|
1520
|
+
```
|
1521
|
+
|
1522
|
+
### Variable Substitution in Strings
|
1523
|
+
|
1524
|
+
Variable substitutions within strings are handled through regex extraction:
|
1525
|
+
|
1526
|
+
```python
|
1527
|
+
def _parse_say_command(self) -> SayCommand:
|
1528
|
+
"""Parse: say "message with $variable<scope>$";"""
|
1529
|
+
self._expect(TokenType.IDENTIFIER, "Expected 'say' keyword")
|
1530
|
+
self._expect(TokenType.QUOTE, "Expected opening quote for say message")
|
1531
|
+
|
1532
|
+
# Get string content (includes variable substitutions)
|
1533
|
+
if self._peek().type == TokenType.IDENTIFIER:
|
1534
|
+
message = self._peek().value
|
1535
|
+
self._advance()
|
1536
|
+
else:
|
1537
|
+
message = ""
|
1538
|
+
|
1539
|
+
# Extract variables using regex pattern
|
1540
|
+
variables = []
|
1541
|
+
import re
|
1542
|
+
var_pattern = r'\$([a-zA-Z_][a-zA-Z0-9_]*<[^>]+>)\$'
|
1543
|
+
matches = re.findall(var_pattern, message)
|
1544
|
+
|
1545
|
+
for match in matches:
|
1546
|
+
if '<' in match and '>' in match:
|
1547
|
+
name = match[:match.index('<')]
|
1548
|
+
scope = match[match.index('<'):match.index('>')+1]
|
1549
|
+
variables.append(VariableSubstitution(name=name, scope=scope))
|
1550
|
+
|
1551
|
+
return SayCommand(message=message, variables=variables)
|
1552
|
+
```
|
1553
|
+
|
1554
|
+
### Error Recovery and Context
|
1555
|
+
|
1556
|
+
The parser provides detailed error information:
|
1557
|
+
|
1558
|
+
```python
|
1559
|
+
def _error(self, message: str, suggestion: str):
|
1560
|
+
"""Raise a parser error with full context."""
|
1561
|
+
if self._is_at_end():
|
1562
|
+
line = 1
|
1563
|
+
column = 1
|
1564
|
+
line_content = "end of file"
|
1565
|
+
else:
|
1566
|
+
token = self._peek()
|
1567
|
+
line = token.line
|
1568
|
+
column = token.column
|
1569
|
+
line_content = token.value
|
1570
|
+
|
1571
|
+
raise MDLParserError(
|
1572
|
+
message=message,
|
1573
|
+
file_path=self.source_file,
|
1574
|
+
line=line,
|
1575
|
+
column=column,
|
1576
|
+
line_content=line_content,
|
1577
|
+
suggestion=suggestion
|
1578
|
+
)
|
1579
|
+
```
|
1580
|
+
|
1581
|
+
### Parser Extensibility
|
1582
|
+
|
1583
|
+
The parser is designed for easy extension:
|
1584
|
+
|
1585
|
+
#### **Adding New Constructs**
|
1586
|
+
```python
|
1587
|
+
def _parse_new_construct(self) -> NewNode:
|
1588
|
+
"""Parse new language construct."""
|
1589
|
+
# Implementation here
|
1590
|
+
pass
|
1591
|
+
|
1592
|
+
# Add to _parse_program method:
|
1593
|
+
elif self._peek().type == TokenType.NEW_KEYWORD:
|
1594
|
+
statements.append(self._parse_new_construct())
|
1595
|
+
```
|
1596
|
+
|
1597
|
+
#### **Adding New Expression Types**
|
1598
|
+
```python
|
1599
|
+
def _parse_primary(self) -> Any:
|
1600
|
+
"""Parse primary expressions."""
|
1601
|
+
if self._peek().type == TokenType.NEW_TYPE:
|
1602
|
+
return self._parse_new_expression()
|
1603
|
+
# ... existing cases
|
1604
|
+
```
|
1605
|
+
|
1606
|
+
### Performance Optimizations
|
1607
|
+
|
1608
|
+
The parser includes several performance optimizations:
|
1609
|
+
|
1610
|
+
1. **Token Lookahead**: Efficient `_peek()` method for lookahead
|
1611
|
+
2. **Early Exit**: Quick checks for common token types
|
1612
|
+
3. **Memory Efficiency**: Minimal object creation during parsing
|
1613
|
+
4. **Error Recovery**: Fast error detection and reporting
|
1614
|
+
|
1615
|
+
### Integration with Lexer
|
1616
|
+
|
1617
|
+
The parser works seamlessly with the lexer:
|
1618
|
+
|
1619
|
+
```python
|
1620
|
+
def parse(self, source: str) -> Program:
|
1621
|
+
"""Parse MDL source code into an AST."""
|
1622
|
+
# Lex the source into tokens
|
1623
|
+
lexer = MDLLexer(self.source_file)
|
1624
|
+
self.tokens = lexer.lex(source)
|
1625
|
+
self.current = 0
|
1626
|
+
|
1627
|
+
# Parse the program
|
1628
|
+
return self._parse_program()
|
1629
|
+
```
|
1630
|
+
|
1631
|
+
This architecture ensures that:
|
1632
|
+
- **Lexical errors** are caught early with detailed context
|
1633
|
+
- **Parsing errors** provide helpful recovery suggestions
|
1634
|
+
- **AST construction** is robust and handles edge cases
|
1635
|
+
- **Error reporting** is consistent across the entire pipeline
|
1636
|
+
|
1637
|
+
The parsing system provides a solid foundation for the MDL compiler, ensuring that all language constructs are properly understood and can be translated into Minecraft datapack commands.
|