flaremc 0.2.0__tar.gz → 0.2.2__tar.gz

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.
Files changed (40) hide show
  1. {flaremc-0.2.0 → flaremc-0.2.2}/PKG-INFO +1 -1
  2. {flaremc-0.2.0 → flaremc-0.2.2}/flare/__init__.py +2 -2
  3. {flaremc-0.2.0 → flaremc-0.2.2}/flare/cli.py +49 -9
  4. {flaremc-0.2.0 → flaremc-0.2.2}/flare/command_parser.py +18 -0
  5. {flaremc-0.2.0 → flaremc-0.2.2}/flare/compiler.py +3 -18
  6. {flaremc-0.2.0 → flaremc-0.2.2}/flare/context.py +107 -26
  7. {flaremc-0.2.0 → flaremc-0.2.2}/flare/control_flow.py +56 -83
  8. {flaremc-0.2.0 → flaremc-0.2.2}/flare/execute_modifiers.py +34 -0
  9. {flaremc-0.2.0 → flaremc-0.2.2}/flare/preprocessor.py +153 -55
  10. flaremc-0.2.2/flare/validator/__init__.py +1 -0
  11. flaremc-0.2.2/flare/validator/core.py +89 -0
  12. flaremc-0.2.2/flare/validator/matchers.py +160 -0
  13. flaremc-0.2.2/flare/validator/parser.py +68 -0
  14. flaremc-0.2.2/flare/validator/schema.py +29 -0
  15. {flaremc-0.2.0 → flaremc-0.2.2}/flare/variables/core.py +29 -5
  16. {flaremc-0.2.0 → flaremc-0.2.2}/flare/variables/nbt.py +195 -98
  17. {flaremc-0.2.0 → flaremc-0.2.2}/flare/variables/score.py +26 -21
  18. {flaremc-0.2.0 → flaremc-0.2.2}/flare/variables/selector.py +13 -25
  19. {flaremc-0.2.0 → flaremc-0.2.2}/flaremc.egg-info/PKG-INFO +1 -1
  20. {flaremc-0.2.0 → flaremc-0.2.2}/flaremc.egg-info/SOURCES.txt +5 -0
  21. {flaremc-0.2.0 → flaremc-0.2.2}/pyproject.toml +1 -1
  22. {flaremc-0.2.0 → flaremc-0.2.2}/LICENSE +0 -0
  23. {flaremc-0.2.0 → flaremc-0.2.2}/README.md +0 -0
  24. {flaremc-0.2.0 → flaremc-0.2.2}/flare/__main__.py +0 -0
  25. {flaremc-0.2.0 → flaremc-0.2.2}/flare/math.py +0 -0
  26. {flaremc-0.2.0 → flaremc-0.2.2}/flare/nbt_schema.py +0 -0
  27. {flaremc-0.2.0 → flaremc-0.2.2}/flare/types.py +0 -0
  28. {flaremc-0.2.0 → flaremc-0.2.2}/flare/variables/__init__.py +0 -0
  29. {flaremc-0.2.0 → flaremc-0.2.2}/flare/variables/bigscore.py +0 -0
  30. {flaremc-0.2.0 → flaremc-0.2.2}/flare/variables/complex.py +0 -0
  31. {flaremc-0.2.0 → flaremc-0.2.2}/flare/variables/float32.py +0 -0
  32. {flaremc-0.2.0 → flaremc-0.2.2}/flare/variables/float64.py +0 -0
  33. {flaremc-0.2.0 → flaremc-0.2.2}/flare/variables/storage.py +0 -0
  34. {flaremc-0.2.0 → flaremc-0.2.2}/flaremc.egg-info/dependency_links.txt +0 -0
  35. {flaremc-0.2.0 → flaremc-0.2.2}/flaremc.egg-info/entry_points.txt +0 -0
  36. {flaremc-0.2.0 → flaremc-0.2.2}/flaremc.egg-info/requires.txt +0 -0
  37. {flaremc-0.2.0 → flaremc-0.2.2}/flaremc.egg-info/top_level.txt +0 -0
  38. {flaremc-0.2.0 → flaremc-0.2.2}/flarevsc/node_modules/flatted/python/flatted.py +0 -0
  39. {flaremc-0.2.0 → flaremc-0.2.2}/setup.cfg +0 -0
  40. {flaremc-0.2.0 → flaremc-0.2.2}/tests/test_flare.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flaremc
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Flare datapack compiler framework
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -1,7 +1,7 @@
1
1
  from .compiler import _flatten_and, _eval_to_bool_score, _compile_relational
2
2
  from .context import namespace, export, tick, push_context, runcommand, files, temp_obj, constant_obj, vars_obj, \
3
- constants, _flare_assign, _flare_print, dbg, _flare_return
4
- from .control_flow import _flare_if, _flare_while, _flare_for, _flare_with
3
+ constants, _flare_assign, _flare_aug_assign, _flare_print, dbg, _flare_return
4
+ from .control_flow import _flare_if, _flare_while, _flare_for, _flare_with, _flare_break, _flare_continue
5
5
  from .execute_modifiers import _as, at, positioned, aligned, facing, anchored, rotated, dimension, applyon, on, summon, \
6
6
  store, ExecuteChain, StoreExecuteChain
7
7
  from .math import round_, floor, ceil
@@ -16,6 +16,8 @@ from watchdog.observers import Observer
16
16
  from . import context
17
17
  from .preprocessor import FlareTransformer, CallGraphAnalyzer, preprocess_minecraft_commands
18
18
 
19
+ build_lock = threading.RLock()
20
+
19
21
 
20
22
  def init_project(path: str):
21
23
  p = Path(path)
@@ -42,7 +44,7 @@ def init_project(path: str):
42
44
  print(f"Created {json_path.absolute()}")
43
45
 
44
46
 
45
- def build_datapack(file_path: str):
47
+ def _build_datapack_inner(file_path: str, cli_overrides: dict = None):
46
48
  p = Path(file_path).parent
47
49
  json_path = p / "flare.json"
48
50
 
@@ -50,13 +52,21 @@ def build_datapack(file_path: str):
50
52
  with open(json_path, "r") as f:
51
53
  config = json.load(f)
52
54
  else:
53
- config = {"namespace": "flare", "pack_format": 15, "description": "A Flare datapack", "build_dir": "dist"}
55
+ config = {"namespace": "flare", "pack_format": 15, "description": "A Flare datapack", "build_dir": "dist",
56
+ "validation_level": "strict", "minecraft_version": "1.20.4"}
57
+
58
+ if cli_overrides:
59
+ config.update(cli_overrides)
54
60
 
55
61
  namespace = config.get("namespace", "flare")
56
62
  build_dir = Path(config.get("build_dir", "dist"))
57
63
  if not build_dir.is_absolute():
58
64
  build_dir = p / build_dir
59
65
 
66
+ context.validation_level = config.get("validation_level", "strict")
67
+ context.minecraft_version = config.get("minecraft_version", "1.20.4")
68
+ context.nbt_schema_missing = config.get("nbt_schema_missing", "error")
69
+
60
70
  context.reset_context()
61
71
  context._current_namespace = namespace
62
72
 
@@ -85,11 +95,14 @@ def build_datapack(file_path: str):
85
95
 
86
96
  global_env = {"__name__": "__main__", "__file__": abs_path}
87
97
  exec(
88
- "from flare import _flare_assign, _flare_if, _flare_while, _flare_for, _flare_with, runcommand, _flare_return\n"
98
+ "from flare import _flare_assign, _flare_aug_assign, _flare_if, _flare_while, _flare_for, _flare_with, runcommand, _flare_return, _flare_break, _flare_continue\n"
89
99
  "from flare.command_parser import interpolate_command\n"
90
100
  "from flare import _flare_print as print, selector, _as, at, positioned, aligned, facing, anchored, rotated, dimension, applyon, on, summon, store\n"
91
- "from flare import nbt, score, fixed, nbtintarray, dbg, export, namespace",
92
- global_env)
101
+ "from flare import nbt, score, fixed, tagged, ref, getscore, storage, array, byte, boolean, short, long, double\n"
102
+ "from flare import nbtbyte, nbtbool, nbtshort, nbtint, nbtlong, nbtfloat, nbtdouble, nbtstr, nbtlist, nbtdict, nbtbytearray, nbtintarray, nbtlongarray\n"
103
+ "from flare import round_, floor, ceil\n"
104
+ "from flare.math import *\n"
105
+ "from flare import dbg, export, namespace, tick", global_env)
93
106
 
94
107
  exec(compile(tree, abs_path, "exec"), global_env)
95
108
  sys.path.pop(0)
@@ -100,6 +113,9 @@ def build_datapack(file_path: str):
100
113
 
101
114
  new_modules = set(sys.modules.keys()) - old_modules
102
115
  watch_files = {os.path.abspath(file_path)}
116
+ if os.path.exists(p / "flare.json"):
117
+ watch_files.add(os.path.abspath(p / "flare.json"))
118
+
103
119
  for mod_name in new_modules:
104
120
  mod = sys.modules.get(mod_name)
105
121
  if mod and getattr(mod, "__file__", None):
@@ -157,6 +173,11 @@ def build_datapack(file_path: str):
157
173
  return True, watch_files, build_dir
158
174
 
159
175
 
176
+ def build_datapack(file_path: str, cli_overrides: dict = None):
177
+ with build_lock:
178
+ return _build_datapack_inner(file_path, cli_overrides)
179
+
180
+
160
181
  class WatcherHandler(FileSystemEventHandler):
161
182
  def __init__(self, cli_args, watch_files):
162
183
  self.cli_args = cli_args
@@ -195,8 +216,27 @@ def main():
195
216
  parser.add_argument("--watch", action="store_true", help="Watch for file changes and rebuild")
196
217
  parser.add_argument("--run", nargs="?", const="-1", default=None,
197
218
  help="Run the compiled datapack in mcemu. Optionally specify a timeout in seconds.")
198
-
199
- args = parser.parse_args()
219
+ parser.add_argument("--nbt-schema-missing", choices=["error", "warning", "ignore"], default="error",
220
+ help="Action when indexing an NBT path that does not exist in the attached schema.")
221
+
222
+ args, unknown_args = parser.parse_known_args()
223
+
224
+ cli_overrides = {}
225
+ if hasattr(args, 'nbt_schema_missing'):
226
+ cli_overrides["nbt_schema_missing"] = args.nbt_schema_missing
227
+ i = 0
228
+ while i < len(unknown_args):
229
+ arg = unknown_args[i]
230
+ if arg.startswith("--"):
231
+ key = arg[2:].replace("-", "_")
232
+ if i + 1 < len(unknown_args) and not unknown_args[i + 1].startswith("--"):
233
+ cli_overrides[key] = unknown_args[i + 1]
234
+ i += 2
235
+ else:
236
+ cli_overrides[key] = True
237
+ i += 1
238
+ else:
239
+ i += 1
200
240
 
201
241
  is_init = args.target == "init"
202
242
  if is_init:
@@ -220,7 +260,7 @@ def main():
220
260
  print(f"Error: Target file {file_path} not found.")
221
261
  return
222
262
 
223
- success, watch_files, build_dir = build_datapack(file_path)
263
+ success, watch_files, build_dir = build_datapack(file_path, cli_overrides)
224
264
 
225
265
  emu_thread = None
226
266
  running = True
@@ -271,7 +311,7 @@ def main():
271
311
  running = False
272
312
  emu_thread.join()
273
313
 
274
- success, new_watch_files, build_dir = build_datapack(file_path)
314
+ success, new_watch_files, build_dir = build_datapack(file_path, cli_overrides)
275
315
 
276
316
  if success and new_watch_files != watch_files:
277
317
  observer.unschedule_all()
@@ -74,6 +74,15 @@ def interpolate_command(command: str, local_vars: dict, global_vars: dict) -> st
74
74
  output.append(val.addr)
75
75
  elif hasattr(val, "target"):
76
76
  output.append(val.target)
77
+ elif isinstance(val, dict) and output and output[-1].endswith("**"):
78
+ output[-1] = output[-1][:-2]
79
+ items = []
80
+ for k, v in val.items():
81
+ if isinstance(k, str) and not re.match(r'^[a-zA-Z0-9_\-\.]+$', k):
82
+ k = json.dumps(k)
83
+ v_str = json.dumps(v) if isinstance(v, (str, dict, list)) else str(v)
84
+ items.append(f"{k}: {v_str}")
85
+ output.append(", ".join(items))
77
86
  else:
78
87
  output.append(str(val))
79
88
  elif not is_key and ident in global_vars:
@@ -82,6 +91,15 @@ def interpolate_command(command: str, local_vars: dict, global_vars: dict) -> st
82
91
  output.append(val.addr)
83
92
  elif hasattr(val, "target"):
84
93
  output.append(val.target)
94
+ elif isinstance(val, dict) and output and output[-1].endswith("**"):
95
+ output[-1] = output[-1][:-2]
96
+ items = []
97
+ for k, v in val.items():
98
+ if isinstance(k, str) and not re.match(r'^[a-zA-Z0-9_\-\.]+$', k):
99
+ k = json.dumps(k)
100
+ v_str = json.dumps(v) if isinstance(v, (str, dict, list)) else str(v)
101
+ items.append(f"{k}: {v_str}")
102
+ output.append(", ".join(items))
85
103
  else:
86
104
  output.append(str(val))
87
105
  else:
@@ -1,7 +1,6 @@
1
1
  from . import context as ctx
2
2
  from .context import runcommand, temp_obj
3
- from .types import NBTType
4
- from .variables import score, nbt, BinaryOp, UnaryOp, getscore
3
+ from .variables import score, BinaryOp, UnaryOp, getscore
5
4
 
6
5
 
7
6
  def _compile_relational(node, invert=False):
@@ -80,22 +79,8 @@ def _eval_to_bool_score(node):
80
79
 
81
80
 
82
81
  def _flatten_and(node, invert=False):
83
- if not isinstance(node, (BinaryOp, UnaryOp)):
84
- if isinstance(node, nbt) and (node.is_sequence() or node.type == NBTType.String):
85
- node = BinaryOp(node.length(), 0, "ne")
86
- else:
87
- node = BinaryOp(node, 0, "ne")
88
- if isinstance(node, UnaryOp) and node.op == "neg":
89
- node = BinaryOp(node, 0, "ne")
90
- if isinstance(node, UnaryOp) and node.op == "not":
91
- return _flatten_and(node.operand, not invert)
92
- if isinstance(node, BinaryOp):
93
- if node.op == "and" and not invert:
94
- return _flatten_and(node.left, invert) + _flatten_and(node.right, invert)
95
- if node.op == "or" and invert:
96
- return _flatten_and(node.left, invert) + _flatten_and(node.right, invert)
97
- if node.op in ("eq", "ne", "lt", "le", "gt", "ge"):
98
- return [_compile_relational(node, invert)]
82
+ if hasattr(node, "__branch__"):
83
+ return node.__branch__(invert)
99
84
 
100
85
  dest = _eval_to_bool_score(node)
101
86
  keyword = "unless" if invert else "if"
@@ -18,11 +18,16 @@ _constant_offset = 0
18
18
  _recursive_functions = set()
19
19
  _in_recursive_context = False
20
20
  return_types = {}
21
+ has_returns = {}
21
22
  _logical_func = None
22
23
 
24
+ validation_level = "strict"
25
+ minecraft_version = "1.20.4"
26
+ nbt_schema_missing = "error"
27
+
23
28
 
24
29
  def reset_context():
25
- global files, current_file, _current_namespace, functions, constants, _temp_id, _func_id, _objective_offset, _constant_offset
30
+ global files, current_file, _current_namespace, functions, constants, _temp_id, _func_id, _objective_offset, _constant_offset, validation_level, minecraft_version, nbt_schema_missing, _recursive_functions, _in_recursive_context, return_types, _logical_func
26
31
  files = {"main": []}
27
32
  current_file = "main"
28
33
  _current_namespace = "flare"
@@ -91,13 +96,22 @@ def namespace(name: str):
91
96
  _current_namespace = name
92
97
 
93
98
 
94
- def __float_prec(x: float) -> int:
95
- return len(str(x).split(".")[-1])
99
+ from .validator import validate_command, FlareCommandValidationError
96
100
 
97
101
 
98
102
  def runcommand(command: str, local_vars=None, global_vars=None):
99
103
  if local_vars is not None and global_vars is not None:
100
104
  command = interpolate_command(command, local_vars, global_vars)
105
+
106
+ if validation_level != "none":
107
+ try:
108
+ validate_command(command, minecraft_version)
109
+ except FlareCommandValidationError as e:
110
+ if validation_level == "strict":
111
+ raise e
112
+ elif validation_level == "warning":
113
+ print(f"[Flare Compiler Warning] {e}")
114
+
101
115
  files[current_file].append(command)
102
116
 
103
117
 
@@ -117,11 +131,13 @@ def _flare_print(*args):
117
131
  if i > 0:
118
132
  components.append({"text": " "})
119
133
 
120
- if hasattr(arg, '__icopy__') and getattr(type(arg), '__name__', '') in ("BinaryOp", "UnaryOp"):
121
- arg = arg.__icopy__(f"{_current_namespace}_temp_print_{i}")
134
+ if hasattr(arg, "__icopy__") and getattr(type(arg), "__name__", "") in ("BinaryOp", "UnaryOp"):
135
+ global _temp_id
136
+ arg = arg.__icopy__(f"!print_{_temp_id}")
137
+ _temp_id += 1
122
138
 
123
139
  if isinstance(arg, score):
124
- if getattr(arg, 'multiplier', 1.0) != 1.0:
140
+ if getattr(arg, "multiplier", 1.0) != 1.0:
125
141
  scale_str = f"{arg.multiplier:.15f}".rstrip("0")
126
142
  if scale_str.endswith("."):
127
143
  scale_str += "0"
@@ -182,15 +198,15 @@ def export(func=None, *, append=False):
182
198
  for name, param in sig.parameters.items():
183
199
  anno = param.annotation
184
200
 
185
- if hasattr(anno, '__name__') and anno.__name__ in ('score', 'fixed', '_PrecisionScore'):
201
+ if hasattr(anno, "__name__") and anno.__name__ in ("score", "fixed", "_PrecisionScore"):
186
202
  if is_recursive:
187
203
  raise TypeError(
188
204
  f"Recursive function '{func.__name__}' argument '{name}' needs a stack but it's a score.")
189
- if anno.__name__ == '_PrecisionScore' or anno.__name__ == 'fixed':
205
+ if anno.__name__ == "_PrecisionScore" or anno.__name__ == "fixed":
190
206
  kwargs[name] = anno(addr=f"{func.__name__}_{name} {vars_obj}")
191
207
  else:
192
208
  kwargs[name] = score(addr=f"{func.__name__}_{name} {vars_obj}")
193
- elif hasattr(anno, '__name__') and anno.__name__ in ('nbt', '_TypedNBT'):
209
+ elif hasattr(anno, "__name__") and anno.__name__ in ("nbt", "_TypedNBT"):
194
210
  if is_recursive:
195
211
  kwargs[name] = anno(addr=f"storage flare:args {func.__name__}_{name}[-1]")
196
212
  else:
@@ -203,7 +219,8 @@ def export(func=None, *, append=False):
203
219
  if sig.return_annotation is not inspect.Signature.empty:
204
220
  return_types[func_name] = sig.return_annotation
205
221
  else:
206
- return_types[func_name] = None
222
+ return_types[func_name] = "UNKNOWN"
223
+ has_returns[func_name] = False
207
224
 
208
225
  global _in_recursive_context, _logical_func
209
226
  prev_recursive = _in_recursive_context
@@ -247,26 +264,28 @@ def export(func=None, *, append=False):
247
264
  base_addr = f"storage flare:args {func.__name__}_{arg_name}"
248
265
  runcommand(f"data remove {base_addr}[-1]")
249
266
 
250
- ret_anno = sig.return_annotation
251
- if ret_anno is not inspect.Signature.empty:
252
- if hasattr(ret_anno, '__name__') and ret_anno.__name__ in ('score', 'fixed', '_PrecisionScore'):
267
+ ret_anno = return_types.get(func_name, sig.return_annotation)
268
+ if ret_anno == "UNKNOWN":
269
+ return "UNKNOWN_RETURN"
270
+ elif ret_anno is not inspect.Signature.empty and ret_anno is not None:
271
+ if hasattr(ret_anno, "__name__") and ret_anno.__name__ in ("score", "fixed", "_PrecisionScore"):
253
272
  temp_ret = score(addr=f"!ret{_temp_id} {temp_obj}")
254
273
  _temp_id += 1
255
274
  runcommand(
256
- f"scoreboard players operation {temp_ret.addr} = {func_name.replace(':', '_')}_ret {vars_obj}")
257
- if ret_anno.__name__ in ('fixed', '_PrecisionScore'):
275
+ f"scoreboard players operation {temp_ret.addr} = {func_name.replace(":", "_")}_ret {vars_obj}")
276
+ if ret_anno.__name__ in ("fixed", "_PrecisionScore"):
258
277
  pass
259
278
  return temp_ret
260
279
  else:
261
280
  temp_ret = nbt(addr=f"storage flare:temp !ret{_temp_id}")
262
281
  _temp_id += 1
263
282
  runcommand(
264
- f"data modify {temp_ret.addr} set from storage flare:returns {func_name.replace(':', '_')}")
283
+ f"data modify {temp_ret.addr} set from storage flare:returns {func_name.replace(":", "_")}")
265
284
  return temp_ret
266
285
 
267
286
  proxy = ProxyFunction()
268
287
 
269
- func_globals = getattr(func, '__globals__', {})
288
+ func_globals = getattr(func, "__globals__", {})
270
289
  prev_func = func_globals.get(func.__name__)
271
290
  func_globals[func.__name__] = proxy
272
291
 
@@ -281,6 +300,13 @@ def export(func=None, *, append=False):
281
300
  else:
282
301
  func_globals.pop(func.__name__, None)
283
302
 
303
+ if return_types[func_name] == "UNKNOWN":
304
+ if has_returns.get(func_name, False):
305
+ raise TypeError(
306
+ f"Function {func_name} has returns but return type could not be auto-detected. Please add an explicit return type annotation.")
307
+ else:
308
+ return_types[func_name] = None
309
+
284
310
  return proxy
285
311
 
286
312
 
@@ -293,8 +319,11 @@ def _flare_assign(var_name, value, local_env, global_env):
293
319
  target = None
294
320
 
295
321
  if target is not None and hasattr(target, "__iset__"):
296
- target.__iset__(value)
297
- return target
322
+ try:
323
+ target.__iset__(value)
324
+ return target
325
+ except Exception:
326
+ pass
298
327
 
299
328
  if target is None and hasattr(value, "__icopy__"):
300
329
  if "is_recursive" in inspect.signature(value.__icopy__).parameters:
@@ -304,6 +333,35 @@ def _flare_assign(var_name, value, local_env, global_env):
304
333
  return value
305
334
 
306
335
 
336
+ def _flare_aug_assign(var_name, op_name, value, _locals, _globals):
337
+ if var_name in _locals:
338
+ var = _locals[var_name]
339
+ elif var_name in _globals:
340
+ var = _globals[var_name]
341
+ else:
342
+ raise NameError(f"name '{var_name}' is not defined")
343
+
344
+ op_map = {"Add": "__iadd__", "Sub": "__isub__", "Mult": "__imul__", "Div": "__itruediv__", "Mod": "__imod__"}
345
+ method_name = op_map.get(op_name)
346
+
347
+ if hasattr(var, method_name):
348
+ getattr(var, method_name)(value)
349
+ else:
350
+ if op_name == "Add":
351
+ var += value
352
+ elif op_name == "Sub":
353
+ var -= value
354
+ elif op_name == "Mult":
355
+ var *= value
356
+ elif op_name == "Div":
357
+ var /= value
358
+ elif op_name == "Mod":
359
+ var %= value
360
+ _flare_assign(var_name, var, _locals, _globals)
361
+
362
+ return value
363
+
364
+
307
365
  def _flare_return(value):
308
366
  from .variables import score, nbt # avoid circular import
309
367
  func_name = _logical_func
@@ -311,22 +369,45 @@ def _flare_return(value):
311
369
  raise Exception("Return outside of exported function")
312
370
  ret_anno = return_types.get(func_name, None)
313
371
 
314
- if ret_anno is None:
372
+ if isinstance(value, str) and value == "UNKNOWN_RETURN":
373
+ has_returns[func_name] = True
374
+ return
375
+
376
+ ret_anno = return_types.get(func_name, None)
377
+
378
+ if ret_anno == "UNKNOWN":
379
+ if hasattr(value, "_best_leaf"):
380
+ leaf = value._best_leaf()
381
+ else:
382
+ leaf = value
383
+
384
+ if leaf is None:
385
+ ret_anno = type(None)
386
+ elif hasattr(type(leaf), "__name__") and type(leaf).__name__ in ("score", "fixed", "_PrecisionScore", "nbt",
387
+ "_TypedNBT"):
388
+ ret_anno = type(leaf)
389
+ else:
390
+ raise TypeError(f"Cannot auto-detect return type from value of type {type(leaf)}")
391
+ return_types[func_name] = ret_anno
392
+
393
+ has_returns[func_name] = True
394
+
395
+ if ret_anno is None or ret_anno == type(None):
315
396
  if value is not None:
316
397
  raise TypeError(f"Function {func_name} returned a value but has no return type annotation")
317
398
  return
318
399
 
319
- if hasattr(ret_anno, '__name__') and ret_anno.__name__ in ('score', 'fixed', '_PrecisionScore'):
320
- target = score(addr=f"{func_name.replace(':', '_')}_ret {vars_obj}")
400
+ if hasattr(ret_anno, "__name__") and ret_anno.__name__ in ("score", "fixed", "_PrecisionScore"):
401
+ target = score(addr=f"{func_name.replace(":", "_")}_ret {vars_obj}")
321
402
  target.__iset__(value)
322
403
  else:
323
404
  if inspect.isclass(ret_anno) and issubclass(ret_anno, nbt):
324
- target = ret_anno(addr=f"storage flare:returns {func_name.replace(':', '_')}")
405
+ target = ret_anno(addr=f"storage flare:returns {func_name.replace(":", "_")}")
325
406
  else:
326
407
  datatype = None
327
- if hasattr(ret_anno, '__origin__') or isinstance(ret_anno, type):
328
- datatype = getattr(ret_anno, '__origin__', ret_anno)
329
- target = nbt(addr=f"storage flare:returns {func_name.replace(':', '_')}", datatype=datatype)
408
+ if hasattr(ret_anno, "__origin__") or isinstance(ret_anno, type):
409
+ datatype = getattr(ret_anno, "__origin__", ret_anno)
410
+ target = nbt(addr=f"storage flare:returns {func_name.replace(":", "_")}", datatype=datatype)
330
411
  target.__iset__(value)
331
412
 
332
413
  runcommand("return 1")
@@ -1,10 +1,16 @@
1
1
  from . import context as ctx
2
- from .execute_modifiers import ExecuteChain, _as
3
2
  from .compiler import _flatten_and
4
3
  from .context import push_context, runcommand, temp_obj
5
- from .types import NBTType
6
- from .variables import score, nbt
7
- from .variables import selector
4
+ from .variables import score
5
+
6
+
7
+ def _flare_break():
8
+ runcommand(f"scoreboard players set !break {temp_obj} 1")
9
+ runcommand("return 0")
10
+
11
+
12
+ def _flare_continue():
13
+ runcommand("return 0")
8
14
 
9
15
 
10
16
  def _flare_if(*args):
@@ -107,12 +113,27 @@ def _flare_if(*args):
107
113
  runcommand(f"execute if score {ret_temp.addr} matches 1 run return 1")
108
114
 
109
115
 
110
- def _flare_while(cond_func, body_func):
116
+ def _flare_while(cond_func, body_func, orelse_func=None, has_break=False, has_continue=False):
111
117
  func_name = f"{ctx._current_namespace}:while_{ctx._func_id}"
112
118
  ctx._func_id += 1
113
119
 
114
120
  with push_context(func_name):
115
- body_func()
121
+ if has_break or has_continue:
122
+ func_body = f"{ctx._current_namespace}:while_body_{ctx._func_id}"
123
+ ctx._func_id += 1
124
+ with push_context(func_body):
125
+ body_func()
126
+
127
+ ret_body = score(addr=f"!ret{ctx._temp_id} {temp_obj}")
128
+ ctx._temp_id += 1
129
+ runcommand(f"execute store result score {ret_body.addr} run function {func_body}")
130
+ runcommand(f"execute if score {ret_body.addr} matches 1 run return 1")
131
+
132
+ if has_break:
133
+ runcommand(f"execute if score !break {temp_obj} matches 1 run return 0")
134
+ else:
135
+ body_func()
136
+
116
137
  cond = cond_func()
117
138
  conds = _flatten_and(cond)
118
139
  prefix = f"execute {' '.join(conds)}"
@@ -122,6 +143,9 @@ def _flare_while(cond_func, body_func):
122
143
  runcommand(f"execute store result score {ret_temp.addr} {' '.join(conds)} run function {func_name}")
123
144
  runcommand(f"execute if score {ret_temp.addr} matches 1 run return 1")
124
145
 
146
+ if has_break:
147
+ runcommand(f"scoreboard players set !break {temp_obj} 0")
148
+
125
149
  cond_init = cond_func()
126
150
  conds_init = _flatten_and(cond_init)
127
151
  prefix_init = f"execute {' '.join(conds_init)}"
@@ -130,91 +154,40 @@ def _flare_while(cond_func, body_func):
130
154
  runcommand(f"execute store result score {ret_temp_init.addr} {' '.join(conds_init)} run function {func_name}")
131
155
  runcommand(f"execute if score {ret_temp_init.addr} matches 1 run return 1")
132
156
 
157
+ if orelse_func:
158
+ if has_break:
159
+ orelse_name = f"{ctx._current_namespace}:while_else_{ctx._func_id}"
160
+ ctx._func_id += 1
161
+ with push_context(orelse_name):
162
+ orelse_func()
163
+ runcommand(f"execute if score !break {temp_obj} matches 0 run function {orelse_name}")
164
+ else:
165
+ orelse_func()
133
166
 
134
- def _flare_for(iterable, body_func):
135
- if isinstance(iterable, nbt) and iterable.is_sequence():
136
- elem_type = None
137
- if iterable.type == NBTType.ByteArray:
138
- elem_type = NBTType.Byte
139
- elif iterable.type == NBTType.IntArray:
140
- elem_type = NBTType.Int
141
- elif iterable.type == NBTType.LongArray:
142
- elem_type = NBTType.Long
143
-
144
- temp_arr = nbt(addr=f"flare:temp !for_arr_{ctx._temp_id}", datatype=iterable.type)
145
- temp_var = nbt(addr=f"{temp_arr.addr}[0]", datatype=elem_type)
146
- ctx._temp_id += 1
147
-
148
- temp_arr.__iset__(iterable)
149
- length_score = temp_arr.length()
150
-
151
- func_name = f"{ctx._current_namespace}:for_{ctx._func_id}"
152
- ctx._func_id += 1
153
-
154
- with push_context(func_name):
155
- body_func(temp_var)
156
- runcommand(f"data remove {temp_arr.addr}[0]")
157
-
158
- length_score -= 1
159
- ctx.files[func_name].append("return 0")
160
- ret_temp = score(addr=f"!ret{ctx._temp_id} {temp_obj}")
161
- ctx._temp_id += 1
162
- runcommand(
163
- f"execute store result score {ret_temp.addr} if score {length_score.addr} matches 1.. run function {func_name}")
164
- runcommand(f"execute if score {ret_temp.addr} matches 1 run return 1")
165
-
166
- ret_temp_init = score(addr=f"!ret{ctx._temp_id} {temp_obj}")
167
- ctx._temp_id += 1
168
- runcommand(
169
- f"execute store result score {ret_temp_init.addr} if score {length_score.addr} matches 1.. run function {func_name}")
170
- runcommand(f"execute if score {ret_temp_init.addr} matches 1 run return 1")
171
167
 
168
+ def _flare_for(iterable, body_func, orelse_func=None, has_break=False, has_continue=False):
169
+ if hasattr(iterable, "__for__"):
170
+ iterable.__for__(body_func, orelse_func, has_break, has_continue)
172
171
  else:
173
- if isinstance(iterable, selector):
174
- _flare_with(iterable, lambda: body_func(selector("@s")))
175
- else:
176
- for item in iterable:
177
- body_func(item)
172
+ for item in iterable:
173
+ body_func(item)
178
174
 
179
175
 
180
176
  def _flare_with(*args):
181
177
  body_func = args[-1]
182
178
  chains = args[:-1]
183
179
 
184
- combined_fragments = ["execute"]
185
- for chain in chains:
186
- if isinstance(chain, selector):
187
- chain = _as(chain)
188
- if isinstance(chain, ExecuteChain):
189
- if chain.fragments and chain.fragments[0] == "execute":
190
- combined_fragments.extend(chain.fragments[1:])
191
- else:
192
- combined_fragments.extend(chain.fragments)
193
- elif isinstance(chain, str):
194
- combined_fragments.append(chain)
195
-
196
- prefix = " ".join(combined_fragments)
197
-
198
- func_name = f"{ctx._current_namespace}:with_{ctx._func_id}"
199
- ctx._func_id += 1
200
-
201
- with push_context(func_name):
202
- body_func()
203
-
204
- if ctx.files.get(func_name):
205
- if len(ctx.files[func_name]) == 1:
206
- cmd = ctx.files[func_name][0]
207
- del ctx.files[func_name]
208
- if cmd.startswith("execute "):
209
- runcommand(f"{prefix} {cmd[8:]}")
210
- else:
211
- runcommand(f"{prefix} run {cmd}")
180
+ def wrap(idx):
181
+ if idx == len(chains):
182
+ body_func()
212
183
  else:
213
- ctx.files[func_name].append("return 0")
214
- ret_temp = score(addr=f"!ret{ctx._temp_id} {temp_obj}")
215
- ctx._temp_id += 1
216
- if prefix.startswith("execute "):
217
- runcommand(f"execute store result score {ret_temp.addr} {prefix[8:]} run function {func_name}")
184
+ obj = chains[idx]
185
+ if hasattr(obj, "__with__"):
186
+ obj.__with__(lambda: wrap(idx + 1))
187
+ elif isinstance(obj, str):
188
+ from .execute_modifiers import ExecuteChain
189
+ ExecuteChain(obj).__with__(lambda: wrap(idx + 1))
218
190
  else:
219
- runcommand(f"execute store result score {ret_temp.addr} run function {func_name}")
220
- runcommand(f"execute if score {ret_temp.addr} matches 1 run return 1")
191
+ raise TypeError(f"Object of type {type(obj).__name__} does not support __with__")
192
+
193
+ wrap(0)