馃悕馃悕馃悕
1
2import inspect
3import dis
4import traceback
5import hashlib
6import sys
7import os
8import time
9import shutil
10from pathlib import Path
11from argparse import ArgumentParser as ArgParser
12from importlib import import_module, reload
13from time import perf_counter
14
15from lib.log import Logger, inner_log
16from lib.internal.parse import lsnap
17from lib.internal.witchery import try_dump_locals
18
19parser = ArgParser("snakepyt")
20#parser.add_argument("sketch", help="the sketch to run", type=str)
21args = parser.parse_args()
22
23snakepyt_version = (0, 1)
24
25def establish_scheduler():
26 schedule = []
27 def _schedule(fn, args):
28 schedule.append((fn, args))
29 return (schedule, _schedule)
30
31def run(fn, arg, partial_id, outer_scope, log, sources, finalizer=None):
32 scope = dict(outer_scope)
33 schedule, schedule_fn = establish_scheduler()
34 scope["schedule"] = schedule_fn
35 scope["print"] = inner_log(source=fn, indent=4)
36 run_id = partial_id + (arg,)
37
38 args = [] if arg is None else [arg]
39 (success, locals_or_err) = try_dump_locals(fn, sources[fn.__name__], args, {}, scope, log)
40 if success:
41 scope.update(locals_or_err)
42 else:
43 err = locals_or_err
44 errs = try_dump_locals.errs
45 if err == errs.NON_DICT_RETURN:
46 log.indented().log("could not extract locals. check for an early return", mode="warning")
47 else:
48 log.indented().log(locals_or_err, mode="error")
49 return False
50
51
52 for (fn, args) in schedule:
53 if args is not None:
54 for arg in args:
55 run(fn, arg, run_id, scope, log.indented(), sources)
56 else:
57 t0 = perf_counter()
58 log(f"begin {fn.__name__} {run_id}")
59 success = run(fn, None, run_id, scope, log.indented(), sources)
60 log(f"done in {perf_counter() - t0:.3f}s", mode="success" if success else "error")
61 log.blank()
62 if finalizer is not None:
63 try:
64 exec(finalizer, globals=scope)
65 except KeyboardInterrupt:
66 raise
67 except:
68 log.indented().trace(source=fn)
69 return False
70 return True
71
72persistent_state = {}
73persistent_hashes = {}
74def handle_persistent(persistent_fn, module_globals, log, sources):
75 bytecode_hash = hashlib.sha256(persistent_fn.__code__.co_code).hexdigest()
76 if sketch_name in persistent_hashes:
77 if bytecode_hash == persistent_hashes[sketch_name]:
78 return True
79 (success, locals_or_err) = try_dump_locals(persistent_fn, sources[persistent_fn.__name__], [], {}, module_globals, log)
80 if success:
81 persistent_hashes[sketch_name] = bytecode_hash
82 persistent_state.update(locals_or_err)
83 else:
84 log(f"failed to run persistent function: {locals_or_err}", mode="error")
85
86 return success
87
88
89try:
90 username = os.getlogin()
91except:
92 username = None
93
94pyt_print = Logger().mode("success").tag("snakepyt")
95pyt_print(f"hello {username}! <3" if username else "hello! <3")
96pyt_print.blank()
97
98repl_continue = True
99while repl_continue:
100 try:
101 message = pyt_print.input(username)
102 except (KeyboardInterrupt, EOFError):
103 pyt_print.blank().log("goodbye <3").blank()
104 repl_continue = False
105 continue
106
107 if "prefix" in persistent_state:
108 message = " ".join([persistent_state["prefix"], message])
109
110 message = message.lstrip()
111
112 if message.startswith("."):
113 if message.rstrip() == ".":
114 pyt_print(persistent_state)
115 else:
116 segments = [segment.strip() for segment in message.split(".")][1:]
117 selection = ("base scope", persistent_state)
118 for segment in segments:
119 if segment == "":
120 pyt_print("repeated dots (..) are redundant", mode="warning")
121 continue
122 try:
123 selection = (segment, selection[1][segment])
124 pyt_print(f"{selection[0]}: {selection[1]}")
125 except KeyError:
126 pyt_print(f"no \"{segment}\" in {selection[0]}", mode="error")
127 continue
128 except TypeError:
129 pyt_print(f"{selection[0]} is not a scope", mode="error")
130 pyt_print.indented()(f"{selection[0]}: {selection[1]}", mode="info")
131
132 continue
133
134
135 (command, remainder) = lsnap(message)
136
137 if command == "flush":
138 persistent_state = {}
139 persistent_hashes = {}
140 pyt_print("state flushed")
141 continue
142 if command in ["exit", "quit", ":q", ",q"]:
143 pyt_print.blank().log("goodbye <3").blank()
144 repl_continue = False
145 continue
146 if command in ["hello", "hi"]:
147 pyt_print("hiii :3")
148 continue
149 if command == "crash":
150 raise Exception("crashing on purpose :3")
151 continue
152 if command == "run":
153 sketch_name, remainder = lsnap(remainder)
154
155 try:
156 pyt_print(f"loading sketch \"{sketch_name}\"", mode="info")
157 module_name = f"sketch.{sketch_name}"
158 if module_name in sys.modules:
159 del sys.modules[module_name]
160 sketch = import_module(module_name)
161 except ModuleNotFoundError:
162 pyt_print.indented().trace()
163 pyt_print("no such sketch", mode="error", indent=4).blank()
164 continue
165 except KeyboardInterrupt:
166 pyt_print("aborted", mode="info").blank()
167 continue
168 except:
169 pyt_print.indented().trace()
170 continue
171
172 sources = { name : inspect.getsource(member)
173 for name, member in inspect.getmembers(sketch)
174 if inspect.isfunction(member) and member.__module__ == module_name
175 }
176
177 log = pyt_print.tag(sketch_name).mode("info")
178
179 t0 = perf_counter()
180 if hasattr(sketch, "persistent"):
181 if not handle_persistent(sketch.persistent, sketch.__dict__, log, sources):
182 log.blank()
183 continue
184
185 sketch.__dict__.update(persistent_state)
186
187 run_dir = time.strftime(f"%d.%m.%Y/{sketch_name}_t%H.%M.%S")
188 sketch.__dict__["run_dir"] = run_dir
189 Path("out/" + run_dir).mkdir(parents=True, exist_ok=True)
190
191 shutil.copy(sketch.__file__, f"out/{run_dir}/{sketch_name}.py")
192
193 with open(f"out/{run_dir}/.snakepyt", "w") as metadata:
194 metadata.write(f"snakepyt version {snakepyt_version[0]}.{snakepyt_version[1]}\n")
195
196 if hasattr(sketch, "main"):
197 if hasattr(sketch, "final"):
198 finalizer = sketch.final.__code__
199 else:
200 finalizer = None
201 try:
202 run(sketch.main, None, (), sketch.__dict__, log, sources, finalizer)
203 except KeyboardInterrupt:
204 pyt_print.blank().log("aborted", mode="info").blank()
205 continue
206 else:
207 log("sketch has no main function", mode="error", indent=4)
208
209 log(f"finished all runs in {perf_counter() - t0:.3f}s")
210 continue
211
212 pyt_print(f"command: {command}", mode="info")
213