flaremc 0.1.0__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.
- flare/__init__.py +11 -0
- flare/__main__.py +4 -0
- flare/cli.py +311 -0
- flare/compiler.py +102 -0
- flare/context.py +191 -0
- flare/control_flow.py +139 -0
- flare/preprocessor.py +179 -0
- flare/types.py +37 -0
- flare/variables.py +1193 -0
- flaremc-0.1.0.dist-info/METADATA +235 -0
- flaremc-0.1.0.dist-info/RECORD +16 -0
- flaremc-0.1.0.dist-info/WHEEL +5 -0
- flaremc-0.1.0.dist-info/entry_points.txt +2 -0
- flaremc-0.1.0.dist-info/licenses/LICENSE +21 -0
- flaremc-0.1.0.dist-info/top_level.txt +2 -0
- flarevsc/node_modules/flatted/python/flatted.py +144 -0
flare/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .compiler import _flatten_and, _eval_to_bool_score, _compile_relational
|
|
2
|
+
from .context import namespace, export, tick, push_context, runcommand, files, temp_obj, constant_obj, vars_obj, \
|
|
3
|
+
constants, _flare_assign, _flare_print, dbg
|
|
4
|
+
from .control_flow import _flare_if, _flare_while, _flare_for
|
|
5
|
+
from .types import NBTType, byte, boolean, short, long, double
|
|
6
|
+
from .variables import score, nbt, fixed, ref, getscore, nbtbyte, nbtbool, nbtshort, nbtint, nbtlong, nbtfloat, \
|
|
7
|
+
nbtdouble, nbtstr, nbtlist, nbtdict, nbtbytearray, nbtintarray, nbtlongarray
|
|
8
|
+
|
|
9
|
+
__all__ = ["namespace", "export", "tick", "score", "nbt", "fixed", "ref", "getscore", "_flare_print", "dbg", "nbtbyte", "nbtbool",
|
|
10
|
+
"nbtshort", "nbtint", "nbtlong", "nbtfloat", "nbtdouble", "nbtstr", "nbtlist", "nbtdict", "nbtbytearray",
|
|
11
|
+
"nbtintarray", "nbtlongarray"]
|
flare/__main__.py
ADDED
flare/cli.py
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import ast
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from watchdog.events import FileSystemEventHandler
|
|
11
|
+
from watchdog.observers import Observer
|
|
12
|
+
|
|
13
|
+
from flare import context
|
|
14
|
+
from flare.preprocessor import FlareTransformer, preprocess_minecraft_commands
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def init_project(path: str):
|
|
18
|
+
p = Path(path)
|
|
19
|
+
p.mkdir(parents=True, exist_ok=True)
|
|
20
|
+
json_path = p / "flare.json"
|
|
21
|
+
|
|
22
|
+
if json_path.exists():
|
|
23
|
+
print(f"Project already initialized at {p.absolute()}")
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
print("Initializing Flare project...")
|
|
27
|
+
try:
|
|
28
|
+
namespace = input("Namespace [flare]: ").strip() or "flare"
|
|
29
|
+
pack_format = input("Pack format [15]: ").strip() or "15"
|
|
30
|
+
description = input("Description [A Flare datapack]: ").strip() or "A Flare datapack"
|
|
31
|
+
except (KeyboardInterrupt, EOFError):
|
|
32
|
+
print("\nInitialization cancelled.")
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
config = {"namespace": namespace, "pack_format": int(pack_format), "description": description, "build_dir": "dist"}
|
|
36
|
+
|
|
37
|
+
with open(json_path, "w") as f:
|
|
38
|
+
json.dump(config, f, indent=4)
|
|
39
|
+
print(f"Created {json_path.absolute()}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def build_datapack(file_path: str):
|
|
43
|
+
p = Path(file_path).parent
|
|
44
|
+
json_path = p / "flare.json"
|
|
45
|
+
|
|
46
|
+
if json_path.exists():
|
|
47
|
+
with open(json_path, "r") as f:
|
|
48
|
+
config = json.load(f)
|
|
49
|
+
else:
|
|
50
|
+
config = {"namespace": "flare", "pack_format": 15, "description": "A Flare datapack", "build_dir": "dist"}
|
|
51
|
+
|
|
52
|
+
namespace = config.get("namespace", "flare")
|
|
53
|
+
build_dir = Path(config.get("build_dir", "dist"))
|
|
54
|
+
if not build_dir.is_absolute():
|
|
55
|
+
build_dir = p / build_dir
|
|
56
|
+
|
|
57
|
+
context.reset_context()
|
|
58
|
+
context._current_namespace = namespace
|
|
59
|
+
|
|
60
|
+
print(f"Compiling {file_path}...")
|
|
61
|
+
|
|
62
|
+
old_modules = set(sys.modules.keys())
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
abs_path = os.path.abspath(file_path)
|
|
66
|
+
sys.path.insert(0, os.path.dirname(abs_path))
|
|
67
|
+
|
|
68
|
+
with open(abs_path, "r") as f:
|
|
69
|
+
source = f.read()
|
|
70
|
+
|
|
71
|
+
source = preprocess_minecraft_commands(source)
|
|
72
|
+
|
|
73
|
+
tree = ast.parse(source, abs_path)
|
|
74
|
+
transformer = FlareTransformer()
|
|
75
|
+
tree = transformer.visit(tree)
|
|
76
|
+
ast.fix_missing_locations(tree)
|
|
77
|
+
|
|
78
|
+
global_env = {"__name__": "__main__", "__file__": abs_path}
|
|
79
|
+
exec("from flare import _flare_assign, _flare_if, _flare_while, _flare_for, runcommand", global_env)
|
|
80
|
+
|
|
81
|
+
exec(compile(tree, abs_path, "exec"), global_env)
|
|
82
|
+
sys.path.pop(0)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
print(f"Build failed: {e}")
|
|
85
|
+
import traceback
|
|
86
|
+
traceback.print_exc()
|
|
87
|
+
return False, set(), None
|
|
88
|
+
|
|
89
|
+
new_modules = set(sys.modules.keys()) - old_modules
|
|
90
|
+
watch_files = {os.path.abspath(file_path)}
|
|
91
|
+
for mod_name in new_modules:
|
|
92
|
+
mod = sys.modules.get(mod_name)
|
|
93
|
+
if mod and getattr(mod, "__file__", None):
|
|
94
|
+
mod_file = os.path.abspath(mod.__file__)
|
|
95
|
+
if (mod_file.endswith(".fl") or mod_file.endswith(
|
|
96
|
+
".py")) and "site-packages" not in mod_file and "lib/python" not in mod_file:
|
|
97
|
+
watch_files.add(mod_file)
|
|
98
|
+
|
|
99
|
+
if build_dir.exists():
|
|
100
|
+
shutil.rmtree(build_dir)
|
|
101
|
+
|
|
102
|
+
build_dir.mkdir(parents=True, exist_ok=True)
|
|
103
|
+
|
|
104
|
+
with open(build_dir / "pack.mcmeta", "w") as f:
|
|
105
|
+
json.dump({"pack": {"pack_format": config.get("pack_format", 15),
|
|
106
|
+
"description": config.get("description", "A Flare datapack")}}, f, indent=4)
|
|
107
|
+
|
|
108
|
+
tags = {"tick": [], "load": []}
|
|
109
|
+
|
|
110
|
+
load_key = f"{context._current_namespace}:load"
|
|
111
|
+
if "main" in context.files:
|
|
112
|
+
if load_key not in context.files:
|
|
113
|
+
context.files[load_key] = []
|
|
114
|
+
context.files[load_key].extend(context.files.pop("main"))
|
|
115
|
+
|
|
116
|
+
for filename, lines in context.files.items():
|
|
117
|
+
if not lines and filename != "main":
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
if filename.endswith(":tick"):
|
|
121
|
+
tags["tick"].append(filename)
|
|
122
|
+
elif filename.endswith(":load"):
|
|
123
|
+
tags["load"].append(filename)
|
|
124
|
+
|
|
125
|
+
if ":" in filename:
|
|
126
|
+
ns, name = filename.split(":", 1)
|
|
127
|
+
file_p = build_dir / "data" / ns / "functions" / f"{name}.mcfunction"
|
|
128
|
+
else:
|
|
129
|
+
file_p = build_dir / "data" / context._current_namespace / "functions" / f"{filename}.mcfunction"
|
|
130
|
+
|
|
131
|
+
file_p.parent.mkdir(parents=True, exist_ok=True)
|
|
132
|
+
with open(file_p, "w") as f:
|
|
133
|
+
for line in lines:
|
|
134
|
+
f.write(f"{line}\n")
|
|
135
|
+
|
|
136
|
+
tag_dir = build_dir / "data" / "minecraft" / "tags" / "functions"
|
|
137
|
+
for tag_name, tag_funcs in tags.items():
|
|
138
|
+
if tag_funcs:
|
|
139
|
+
tag_dir.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
tag_path = tag_dir / f"{tag_name}.json"
|
|
141
|
+
with open(tag_path, "w") as f:
|
|
142
|
+
json.dump({"values": tag_funcs}, f, indent=4)
|
|
143
|
+
|
|
144
|
+
print(f"Successfully built datapack to {build_dir.absolute()}")
|
|
145
|
+
return True, watch_files, build_dir
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class WatcherHandler(FileSystemEventHandler):
|
|
149
|
+
def __init__(self, cli_args, watch_files):
|
|
150
|
+
self.cli_args = cli_args
|
|
151
|
+
self.watch_files = watch_files
|
|
152
|
+
self.rebuild_pending = False
|
|
153
|
+
|
|
154
|
+
def on_modified(self, event):
|
|
155
|
+
if not event.is_directory and event.src_path in self.watch_files:
|
|
156
|
+
self.rebuild_pending = True
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def get_tags(build_dir: Path, tag_type: str, tag_name: str) -> list[str]:
|
|
160
|
+
tag_path = build_dir / "data" / "minecraft" / "tags" / tag_type / f"{tag_name}.json"
|
|
161
|
+
if tag_path.exists():
|
|
162
|
+
try:
|
|
163
|
+
with open(tag_path, "r") as f:
|
|
164
|
+
data = json.load(f)
|
|
165
|
+
return data.get("values", [])
|
|
166
|
+
except Exception:
|
|
167
|
+
pass
|
|
168
|
+
return []
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def run_emulator(build_dir: Path):
|
|
172
|
+
try:
|
|
173
|
+
import mcemu
|
|
174
|
+
except ImportError:
|
|
175
|
+
print("mcemu is not installed. Run `pip install mcemu` to use the --run feature.")
|
|
176
|
+
return None
|
|
177
|
+
print("\n--- Starting mcemu ---")
|
|
178
|
+
try:
|
|
179
|
+
import mcemu.commands
|
|
180
|
+
except ImportError:
|
|
181
|
+
pass
|
|
182
|
+
|
|
183
|
+
emu = mcemu.Emulator()
|
|
184
|
+
emu.load_datapack(str(build_dir.absolute()))
|
|
185
|
+
|
|
186
|
+
return emu
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def main():
|
|
190
|
+
parser = argparse.ArgumentParser(description="Flare CLI Datapack Compiler")
|
|
191
|
+
parser.add_argument("target", nargs="?", default=".",
|
|
192
|
+
help="File to build or directory to init. Use 'init' to initialize in current directory.")
|
|
193
|
+
parser.add_argument("--watch", action="store_true", help="Watch for file changes and rebuild")
|
|
194
|
+
parser.add_argument("--run", nargs="?", const="-1", default=None,
|
|
195
|
+
help="Run the compiled datapack in mcemu. Optionally specify a timeout in seconds.")
|
|
196
|
+
|
|
197
|
+
args = parser.parse_args()
|
|
198
|
+
|
|
199
|
+
is_init = args.target == "init"
|
|
200
|
+
if is_init:
|
|
201
|
+
init_project(".")
|
|
202
|
+
if not args.watch and not args.run:
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
if os.path.isdir(args.target):
|
|
206
|
+
file_path = os.path.join(args.target, "main.fl")
|
|
207
|
+
if not os.path.exists(file_path) and os.path.exists(os.path.join(args.target, "main.py")):
|
|
208
|
+
file_path = os.path.join(args.target, "main.py")
|
|
209
|
+
else:
|
|
210
|
+
if args.target.endswith(".fl") or args.target.endswith(".py"):
|
|
211
|
+
file_path = args.target
|
|
212
|
+
else:
|
|
213
|
+
file_path = f"{args.target}.fl"
|
|
214
|
+
if not os.path.exists(file_path) and os.path.exists(f"{args.target}.py"):
|
|
215
|
+
file_path = f"{args.target}.py"
|
|
216
|
+
|
|
217
|
+
if not os.path.exists(file_path) and not is_init:
|
|
218
|
+
print(f"Error: Target file {file_path} not found.")
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
success, watch_files, build_dir = build_datapack(file_path)
|
|
222
|
+
|
|
223
|
+
emu_thread = None
|
|
224
|
+
running = True
|
|
225
|
+
|
|
226
|
+
def run_loop(emu, timeout):
|
|
227
|
+
try:
|
|
228
|
+
start_time = time.time()
|
|
229
|
+
while running:
|
|
230
|
+
if timeout is not None and timeout >= 0:
|
|
231
|
+
if time.time() - start_time >= timeout:
|
|
232
|
+
break
|
|
233
|
+
if emu:
|
|
234
|
+
emu.tick()
|
|
235
|
+
time.sleep(0.05)
|
|
236
|
+
except KeyboardInterrupt:
|
|
237
|
+
pass
|
|
238
|
+
|
|
239
|
+
import threading
|
|
240
|
+
if success and args.run is not None:
|
|
241
|
+
try:
|
|
242
|
+
timeout = float(args.run) if args.run != "-1" else None
|
|
243
|
+
except ValueError:
|
|
244
|
+
timeout = None
|
|
245
|
+
emu = run_emulator(build_dir)
|
|
246
|
+
if emu:
|
|
247
|
+
emu_thread = threading.Thread(target=run_loop, args=(emu, timeout))
|
|
248
|
+
emu_thread.daemon = True
|
|
249
|
+
emu_thread.start()
|
|
250
|
+
|
|
251
|
+
if args.watch:
|
|
252
|
+
print(f"Watching for changes in {len(watch_files)} files...")
|
|
253
|
+
handler = WatcherHandler(args, watch_files)
|
|
254
|
+
observer = Observer()
|
|
255
|
+
|
|
256
|
+
watch_dirs = set(os.path.dirname(f) for f in watch_files)
|
|
257
|
+
for d in watch_dirs:
|
|
258
|
+
observer.schedule(handler, d, recursive=False)
|
|
259
|
+
|
|
260
|
+
observer.start()
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
while True:
|
|
264
|
+
time.sleep(0.5)
|
|
265
|
+
if handler.rebuild_pending:
|
|
266
|
+
print("\nChange detected. Rebuilding...")
|
|
267
|
+
handler.rebuild_pending = False
|
|
268
|
+
|
|
269
|
+
if emu_thread:
|
|
270
|
+
running = False
|
|
271
|
+
emu_thread.join()
|
|
272
|
+
|
|
273
|
+
success, new_watch_files, build_dir = build_datapack(file_path)
|
|
274
|
+
|
|
275
|
+
if success and new_watch_files != watch_files:
|
|
276
|
+
observer.unschedule_all()
|
|
277
|
+
watch_files = new_watch_files
|
|
278
|
+
handler.watch_files = watch_files
|
|
279
|
+
watch_dirs = set(os.path.dirname(f) for f in watch_files)
|
|
280
|
+
for d in watch_dirs:
|
|
281
|
+
observer.schedule(handler, d, recursive=False)
|
|
282
|
+
|
|
283
|
+
if success and args.run is not None:
|
|
284
|
+
running = True
|
|
285
|
+
try:
|
|
286
|
+
timeout = float(args.run) if args.run != "-1" else None
|
|
287
|
+
except ValueError:
|
|
288
|
+
timeout = None
|
|
289
|
+
emu = run_emulator(build_dir)
|
|
290
|
+
if emu:
|
|
291
|
+
emu_thread = threading.Thread(target=run_loop, args=(emu, timeout))
|
|
292
|
+
emu_thread.daemon = True
|
|
293
|
+
emu_thread.start()
|
|
294
|
+
|
|
295
|
+
except KeyboardInterrupt:
|
|
296
|
+
observer.stop()
|
|
297
|
+
running = False
|
|
298
|
+
print("\nStopped watching.")
|
|
299
|
+
observer.join()
|
|
300
|
+
else:
|
|
301
|
+
if emu_thread:
|
|
302
|
+
try:
|
|
303
|
+
while emu_thread.is_alive():
|
|
304
|
+
time.sleep(0.1)
|
|
305
|
+
except KeyboardInterrupt:
|
|
306
|
+
running = False
|
|
307
|
+
print("\nStopped emulator.")
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
if __name__ == "__main__":
|
|
311
|
+
main()
|
flare/compiler.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from . import context as ctx
|
|
2
|
+
from .context import runcommand, temp_obj
|
|
3
|
+
from .types import NBTType
|
|
4
|
+
from .variables import score, nbt, BinaryOp, UnaryOp, getscore
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _compile_relational(node, invert=False):
|
|
8
|
+
op_map = {"eq": ("if", "="), "ne": ("unless", "="), "lt": ("if", "<"), "le": ("if", "<="), "gt": ("if", ">"),
|
|
9
|
+
"ge": ("if", ">=")}
|
|
10
|
+
if node.op not in op_map:
|
|
11
|
+
raise ValueError(f"Not a relational op: {node.op}")
|
|
12
|
+
|
|
13
|
+
keyword, mcop = op_map[node.op]
|
|
14
|
+
if invert:
|
|
15
|
+
keyword = "unless" if keyword == "if" else "if"
|
|
16
|
+
|
|
17
|
+
left, right = node.left, node.right
|
|
18
|
+
|
|
19
|
+
if not isinstance(left, score):
|
|
20
|
+
if isinstance(left, (int, float)):
|
|
21
|
+
left = getscore(left)
|
|
22
|
+
else:
|
|
23
|
+
t = score(addr=f"!c{ctx._temp_id} {temp_obj}")
|
|
24
|
+
ctx._temp_id += 1
|
|
25
|
+
if isinstance(left, (BinaryOp, UnaryOp)):
|
|
26
|
+
left._eval_into(t)
|
|
27
|
+
else:
|
|
28
|
+
t.__iset__(left)
|
|
29
|
+
left = t
|
|
30
|
+
|
|
31
|
+
if not isinstance(right, score):
|
|
32
|
+
if isinstance(right, (int, float)):
|
|
33
|
+
right = getscore(right, left.multiplier)
|
|
34
|
+
else:
|
|
35
|
+
t = score(addr=f"!c{ctx._temp_id} {temp_obj}", multiplier=left.multiplier)
|
|
36
|
+
ctx._temp_id += 1
|
|
37
|
+
if isinstance(right, (BinaryOp, UnaryOp)):
|
|
38
|
+
right._eval_into(t)
|
|
39
|
+
else:
|
|
40
|
+
t.__iset__(right)
|
|
41
|
+
right = t
|
|
42
|
+
|
|
43
|
+
if left.multiplier != right.multiplier:
|
|
44
|
+
t = score(addr=f"!c{ctx._temp_id} {temp_obj}", multiplier=left.multiplier)
|
|
45
|
+
ctx._temp_id += 1
|
|
46
|
+
t.__iset__(right)
|
|
47
|
+
right = t
|
|
48
|
+
|
|
49
|
+
return f"{keyword} score {left.addr} {mcop} {right.addr}"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _eval_to_bool_score(node):
|
|
53
|
+
dest = score(addr=f"!b{ctx._temp_id} {temp_obj}")
|
|
54
|
+
ctx._temp_id += 1
|
|
55
|
+
runcommand(f"scoreboard players set {dest.addr} 0")
|
|
56
|
+
|
|
57
|
+
if isinstance(node, BinaryOp) and node.op == "or":
|
|
58
|
+
left_conds = _flatten_and(node.left)
|
|
59
|
+
runcommand(f"execute {' '.join(left_conds)} run scoreboard players set {dest.addr} 1")
|
|
60
|
+
right_conds = _flatten_and(node.right)
|
|
61
|
+
runcommand(
|
|
62
|
+
f"execute if score {dest.addr} matches 0 {' '.join(right_conds)} run scoreboard players set {dest.addr} 1")
|
|
63
|
+
return dest
|
|
64
|
+
|
|
65
|
+
if isinstance(node, UnaryOp) and node.op == "not":
|
|
66
|
+
sub_dest = _eval_to_bool_score(node.operand)
|
|
67
|
+
runcommand(f"execute if score {sub_dest.addr} matches 0 run scoreboard players set {dest.addr} 1")
|
|
68
|
+
return dest
|
|
69
|
+
|
|
70
|
+
if isinstance(node, (BinaryOp, UnaryOp)):
|
|
71
|
+
t = score(addr=f"!b{ctx._temp_id} {temp_obj}")
|
|
72
|
+
ctx._temp_id += 1
|
|
73
|
+
node._eval_into(t)
|
|
74
|
+
runcommand(f"execute unless score {t.addr} matches 0 run scoreboard players set {dest.addr} 1")
|
|
75
|
+
return dest
|
|
76
|
+
|
|
77
|
+
if node:
|
|
78
|
+
runcommand(f"scoreboard players set {dest.addr} 1")
|
|
79
|
+
return dest
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
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)]
|
|
99
|
+
|
|
100
|
+
dest = _eval_to_bool_score(node)
|
|
101
|
+
keyword = "unless" if invert else "if"
|
|
102
|
+
return [f"{keyword} score {dest.addr} matches 1"]
|
flare/context.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
files = {"main": []}
|
|
4
|
+
current_file = "main"
|
|
5
|
+
_current_namespace = "flare"
|
|
6
|
+
functions = {}
|
|
7
|
+
constants = {}
|
|
8
|
+
constant_obj = "__flare__constant__"
|
|
9
|
+
vars_obj = "__flare__vars__"
|
|
10
|
+
temp_obj = "__flare__temp__"
|
|
11
|
+
_temp_id = 0
|
|
12
|
+
_func_id = 0
|
|
13
|
+
_objective_offset = 0
|
|
14
|
+
_constant_offset = 0
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def reset_context():
|
|
18
|
+
global files, current_file, _current_namespace, functions, constants, _temp_id, _func_id, _objective_offset, _constant_offset
|
|
19
|
+
files = {"main": []}
|
|
20
|
+
current_file = "main"
|
|
21
|
+
_current_namespace = "flare"
|
|
22
|
+
functions = {}
|
|
23
|
+
constants = {}
|
|
24
|
+
_temp_id = 0
|
|
25
|
+
_func_id = 0
|
|
26
|
+
_objective_offset = 0
|
|
27
|
+
_constant_offset = 0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def ensure_objective(obj: str):
|
|
31
|
+
global _objective_offset, _constant_offset
|
|
32
|
+
if not obj:
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
load_file = f"{_current_namespace}:load"
|
|
36
|
+
if load_file not in files:
|
|
37
|
+
files[load_file] = []
|
|
38
|
+
|
|
39
|
+
cmd = f"scoreboard objectives add {obj} dummy"
|
|
40
|
+
if cmd not in files[load_file]:
|
|
41
|
+
files[load_file].insert(_objective_offset, cmd)
|
|
42
|
+
_objective_offset += 1
|
|
43
|
+
_constant_offset += 1
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def ensure_constant(name: str, obj: str, val: int):
|
|
47
|
+
global _constant_offset
|
|
48
|
+
ensure_objective(obj)
|
|
49
|
+
|
|
50
|
+
load_file = f"{_current_namespace}:load"
|
|
51
|
+
cmd = f"scoreboard players set {name} {obj} {val}"
|
|
52
|
+
if cmd not in files[load_file]:
|
|
53
|
+
files[load_file].insert(_constant_offset, cmd)
|
|
54
|
+
_constant_offset += 1
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class _ContextManager:
|
|
58
|
+
def __init__(self, new_file: str):
|
|
59
|
+
self.new_file = new_file
|
|
60
|
+
self.old_file = None
|
|
61
|
+
|
|
62
|
+
def __enter__(self):
|
|
63
|
+
global current_file
|
|
64
|
+
self.old_file = current_file
|
|
65
|
+
current_file = self.new_file
|
|
66
|
+
if self.new_file not in files:
|
|
67
|
+
files[self.new_file] = []
|
|
68
|
+
|
|
69
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
70
|
+
global current_file
|
|
71
|
+
current_file = self.old_file
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def push_context(name: str):
|
|
75
|
+
return _ContextManager(name)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def namespace(name: str):
|
|
79
|
+
global _current_namespace
|
|
80
|
+
_current_namespace = name
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def __float_prec(x: float) -> int:
|
|
84
|
+
return len(str(x).split(".")[-1])
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def runcommand(command: str):
|
|
88
|
+
files[current_file].append(command)
|
|
89
|
+
|
|
90
|
+
import builtins
|
|
91
|
+
|
|
92
|
+
def dbg(*args):
|
|
93
|
+
processed_str = " ".join(str(arg) for arg in args)
|
|
94
|
+
builtins.print(processed_str)
|
|
95
|
+
_flare_print(processed_str)
|
|
96
|
+
|
|
97
|
+
def _flare_print(*args):
|
|
98
|
+
from .variables import score, nbt # to avoid circular import
|
|
99
|
+
components = []
|
|
100
|
+
for i, arg in enumerate(args):
|
|
101
|
+
if i > 0:
|
|
102
|
+
components.append({"text": " "})
|
|
103
|
+
|
|
104
|
+
if isinstance(arg, score):
|
|
105
|
+
if getattr(arg, 'multiplier', 1.0) != 1.0:
|
|
106
|
+
scale_str = f"{arg.multiplier:.15f}".rstrip("0")
|
|
107
|
+
if scale_str.endswith("."):
|
|
108
|
+
scale_str += "0"
|
|
109
|
+
runcommand(
|
|
110
|
+
f"execute store result storage flare:temp __flare_debug_{i} double {scale_str} run scoreboard players get {arg.addr}")
|
|
111
|
+
components.append({"nbt": f"__flare_debug_{i}", "storage": "flare:temp"})
|
|
112
|
+
else:
|
|
113
|
+
name, obj = arg.addr.split(" ", 1)
|
|
114
|
+
components.append({"score": {"name": name, "objective": obj}})
|
|
115
|
+
elif isinstance(arg, nbt):
|
|
116
|
+
nbt_comp = {"nbt": arg.path or "{}"}
|
|
117
|
+
if arg.path == "":
|
|
118
|
+
nbt_comp["nbt"] = "{}"
|
|
119
|
+
|
|
120
|
+
if arg.target_type == "storage":
|
|
121
|
+
nbt_comp["storage"] = arg.target
|
|
122
|
+
elif arg.target_type == "entity":
|
|
123
|
+
nbt_comp["entity"] = arg.target
|
|
124
|
+
elif arg.target_type == "block":
|
|
125
|
+
nbt_comp["block"] = arg.target
|
|
126
|
+
|
|
127
|
+
if arg.path == "":
|
|
128
|
+
nbt_comp["nbt"] = "{}"
|
|
129
|
+
|
|
130
|
+
components.append(nbt_comp)
|
|
131
|
+
else:
|
|
132
|
+
components.append({"text": str(arg)})
|
|
133
|
+
|
|
134
|
+
if len(components) == 1:
|
|
135
|
+
comp = components[0]
|
|
136
|
+
if "text" in comp and len(comp) == 1:
|
|
137
|
+
cmd_text = json.dumps(comp["text"])
|
|
138
|
+
else:
|
|
139
|
+
cmd_text = json.dumps(comp)
|
|
140
|
+
else:
|
|
141
|
+
cmd_text = json.dumps(components)
|
|
142
|
+
|
|
143
|
+
runcommand(f"tellraw @a {cmd_text}")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def export(func=None, *, append=False):
|
|
147
|
+
if func is None:
|
|
148
|
+
def wrapper(f):
|
|
149
|
+
return export(f, append=append)
|
|
150
|
+
|
|
151
|
+
return wrapper
|
|
152
|
+
|
|
153
|
+
func_name = f"{_current_namespace}:{func.__name__}"
|
|
154
|
+
if func_name in files and not append:
|
|
155
|
+
raise ValueError(f"Function {func_name} already exists. Use @export(append=True) to append.")
|
|
156
|
+
|
|
157
|
+
with push_context(func_name):
|
|
158
|
+
func()
|
|
159
|
+
|
|
160
|
+
return func
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _flare_assign(var_name, value, local_env, global_env):
|
|
164
|
+
if var_name in local_env:
|
|
165
|
+
target = local_env[var_name]
|
|
166
|
+
elif var_name in global_env:
|
|
167
|
+
target = global_env[var_name]
|
|
168
|
+
else:
|
|
169
|
+
target = None
|
|
170
|
+
|
|
171
|
+
if target is not None and hasattr(target, "__iset__"):
|
|
172
|
+
target.__iset__(value)
|
|
173
|
+
return target
|
|
174
|
+
|
|
175
|
+
if target is None and hasattr(value, "__icopy__"):
|
|
176
|
+
return value.__icopy__(varid=f"{_current_namespace}_{var_name}")
|
|
177
|
+
|
|
178
|
+
return value
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def tick(func=None):
|
|
182
|
+
if func is None:
|
|
183
|
+
def wrapper(f):
|
|
184
|
+
return tick(f)
|
|
185
|
+
|
|
186
|
+
return wrapper
|
|
187
|
+
|
|
188
|
+
func_name = f"{_current_namespace}:tick"
|
|
189
|
+
with push_context(func_name):
|
|
190
|
+
func()
|
|
191
|
+
return func
|