|
1 | 1 | # Copyright 2022 iiPython |
2 | | -# x2.3b1 Codename Goos |
3 | 2 |
|
4 | 3 | # Modules |
5 | 4 | import os |
6 | 5 | import sys |
7 | | -import time |
8 | | -import json |
9 | | -import string |
10 | | -from typing import Any, Tuple |
11 | | - |
12 | 6 | from x2 import ( |
13 | | - opmap, |
14 | | - XTMemory, XTDatastore, XTContext, |
15 | | - UnknownOperator, InvalidSection, IllegalSectionName |
| 7 | + load_sections, load_cli, config, |
| 8 | + Interpreter |
16 | 9 | ) |
17 | 10 |
|
18 | 11 | # Initialization |
19 | | -__version__ = "x2.3b1" |
20 | | - |
21 | | -sys.argv = sys.argv[1:] |
22 | | -xt_folder = os.path.join(os.path.dirname(__file__), "x2") |
23 | | - |
24 | | -def check_argv(matches: list) -> bool: |
25 | | - return bool([m for m in matches if m in sys.argv]) |
26 | | - |
27 | | -if check_argv(["-h", "--help"]): |
28 | | - print("usage: x2 [-hv] [--help/version] [file]") |
29 | | - print("-h (--help) shows this message and exits") |
30 | | - print("-v (--version) prints the x2 version and exits") |
31 | | - print() |
32 | | - print("If file is not provided, it is loaded from .xtconfig") |
33 | | - print("Copyright (c) 2022 iiPython + Dm123321_31mD") |
34 | | - sys.exit(0) |
35 | | - |
36 | | -elif check_argv(["-v", "--version"]): |
37 | | - sys.exit(__version__) |
38 | | - |
39 | | -# Load x2 configuration |
40 | | -config = {} |
41 | | -if os.path.isfile(".xtconfig"): |
42 | | - with open(".xtconfig", "r") as f: |
43 | | - config = json.loads(f.read()) |
44 | | - |
45 | | -# x2 Interpreter |
46 | | -class XTInterpreter(object): |
47 | | - def __init__(self, opmap: dict = {}) -> None: |
48 | | - |
49 | | - # Memory initialization |
50 | | - self.memory = XTMemory() |
51 | | - self.memory.interpreter = self |
52 | | - |
53 | | - self.linetrk = [] |
54 | | - self.sections = {} |
55 | | - |
56 | | - self._live = False |
57 | | - self._opmap = opmap |
58 | | - |
59 | | - # Data attributes |
60 | | - self._config = config |
61 | | - self._uptime = time.time() |
62 | | - self._version = __version__ |
63 | | - |
64 | | - def setvar(self, name: str, value: Any, **kwargs) -> Any: |
65 | | - return XTDatastore(self.memory, name, **kwargs).set(value) |
66 | | - |
67 | | - def getvar(self, name: str, **kwargs) -> XTDatastore: |
68 | | - return XTDatastore(self.memory, name, **kwargs) |
69 | | - |
70 | | - def execute(self, line: str, raise_error: bool = False) -> Any: |
71 | | - if self._live and ((self.linetrk and self.linetrk[-1][0] != "<stdin>") or not self.linetrk): |
72 | | - self.linetrk.append({"file": "<stdin>", "path": "<stdin>", "section": "<stdin>.global", "start": 0, "ended": False, "as": "<stdin>"}) |
73 | | - |
74 | | - try: |
75 | | - tokens = self.parseline(line) |
76 | | - try: |
77 | | - trkdata = self.linetrk[-1] |
78 | | - prevline = self.sections[trkdata["section"]]["lines"][trkdata["start"] - self.sections[trkdata["section"]]["start"] - 2] |
79 | | - if prevline[-2:] in [" \\", ".."]: |
80 | | - return None |
81 | | - |
82 | | - except IndexError: |
83 | | - pass |
84 | | - |
85 | | - operator = tokens[0] |
86 | | - if operator not in self._opmap: |
87 | | - raise UnknownOperator(operator) |
88 | | - |
89 | | - return self._opmap[operator](XTContext(self.memory, tokens[1:])) |
90 | | - |
91 | | - except Exception as e: |
92 | | - if raise_error or True: |
93 | | - raise e |
94 | | - |
95 | | - elif config.get("quiet", False): |
96 | | - return None |
97 | | - |
98 | | - print("Exception occured in x2 thread!") |
99 | | - for tracker in self.linetrk: |
100 | | - line = self.sections[tracker['section']]["lines"][tracker['start'] - self.sections[tracker['section']]["start"] - 1].lstrip() |
101 | | - print(f"{tracker['file']} line {tracker['start']}, in {tracker['section'].split('.')[1]}:\n > {line}") |
102 | | - |
103 | | - print(f"\n{type(e).__name__}: {e}") |
104 | | - if not self._live: |
105 | | - os._exit(1) |
106 | | - |
107 | | - def parseline(self, line: str, multiline_offset: int = 0) -> list: |
108 | | - data = {"val": "", "flags": [], "expridx": 0, "line": []} |
109 | | - for idx, char in enumerate(line): |
110 | | - if char == ":" and "qt" not in data["flags"]: |
111 | | - if idx != len(line) - 1 and line[idx + 1] == ":": |
112 | | - break |
113 | | - |
114 | | - if char == ")" and "expr" in data["flags"]: |
115 | | - if data["expridx"] > 1: |
116 | | - data["val"] += ")" |
117 | | - |
118 | | - elif data["expridx"] == 1: |
119 | | - data["flags"].remove("expr") |
120 | | - data["line"].append(f"({data['val']})") |
121 | | - data["val"] = "" |
122 | | - |
123 | | - data["expridx"] -= 1 |
124 | | - |
125 | | - elif char == "(" and "qt" not in data["flags"]: |
126 | | - if "expr" not in data["flags"]: |
127 | | - data["flags"].append("expr") |
128 | | - |
129 | | - data["expridx"] += 1 |
130 | | - if data["expridx"] > 1: |
131 | | - data["val"] += "(" |
132 | | - |
133 | | - elif "expr" in data["flags"]: |
134 | | - data["val"] += char |
135 | | - |
136 | | - elif char == " " and "qt" not in data["flags"]: |
137 | | - if not data["val"]: |
138 | | - continue |
139 | | - |
140 | | - data["line"].append(data["val"]) |
141 | | - data["val"] = "" |
| 12 | +cli = load_cli() # Render CLI |
142 | 13 |
|
143 | | - elif char == "\"" and (line[idx - 1] != "\\" if idx > 0 else True): # Enables quoting with backslash |
144 | | - if "qt" in data["flags"]: |
145 | | - data["line"].append(data["val"] + "\"") |
146 | | - data["val"] = "" |
147 | | - data["flags"].remove("qt") |
| 14 | +# Load filepath |
| 15 | +filepath = cli.filepath |
| 16 | +if filepath is None: |
| 17 | + cli.show_help() |
148 | 18 |
|
149 | | - else: |
150 | | - data["flags"].append("qt") |
151 | | - data["val"] += "\"" |
| 19 | +elif filepath == ".": |
| 20 | + filepath = config.get("main", "main.x2") |
152 | 21 |
|
153 | | - else: |
154 | | - data["val"] += char |
| 22 | +if not os.path.isfile(filepath): |
| 23 | + sys.exit("x2 Exception: no such file") |
155 | 24 |
|
156 | | - # Construct missing data |
157 | | - if data["val"]: |
158 | | - data["line"].append(data["val"]) |
159 | | - data["val"] = "" |
| 25 | +# Load file content |
| 26 | +with open(filepath, "r") as f: |
| 27 | + data = f.read() |
160 | 28 |
|
161 | | - # Push lines |
162 | | - if data["line"][-1] in ["\\", ".."]: |
163 | | - trkdata = self.linetrk[-1] |
164 | | - nextline = self.parseline(self.sections[trkdata[1]]["lines"][trkdata[2] - self.sections[trkdata[1]]["start"] + multiline_offset], multiline_offset + 1) |
165 | | - if data["line"][-1] == "..": |
166 | | - data["line"][-2], nextline = data["line"][-2][:-1] + "\n" + nextline[0][1:], nextline[1:] |
167 | | - |
168 | | - data["line"] = data["line"][:-1] |
169 | | - data["line"] = data["line"] + nextline |
170 | | - |
171 | | - return data["line"] |
172 | | - |
173 | | - def load_sections(self, code: str, filepath: str, namespace: str = None, external: bool = False) -> None: |
174 | | - filename = filepath.replace("\\", "/").split("/")[-1] |
175 | | - if not hasattr(self, "_entrypoint"): |
176 | | - self._entrypoint = filename |
177 | | - |
178 | | - self.memory.vars["file"][filepath] = {} |
179 | | - |
180 | | - fileid = (namespace or filepath.replace("/", ".")).removesuffix(".xt") |
181 | | - dt = { |
182 | | - "active": "global", |
183 | | - "code": [], |
184 | | - "sections": {f"{fileid}.global": { |
185 | | - "lines": [], "priv": False, "file": filename, "path": filepath, |
186 | | - "start": 0, "args": [], "ret": None, "as": fileid |
187 | | - }} |
188 | | - } |
189 | | - for lno, line in enumerate(code.split("\n")): |
190 | | - if line.strip(): |
191 | | - if line[0] == ":" and line[:2] != "::": |
192 | | - ns, sid, priv = f"{fileid}.{dt['active']}", line[1:].split(" ")[0], False |
193 | | - if [c for c in sid if c not in string.ascii_letters + string.digits + "@"]: |
194 | | - raise IllegalSectionName(f"section '{sid}' contains invalid characters") |
195 | | - |
196 | | - elif "@" in sid: |
197 | | - if sid[0] != "@": |
198 | | - raise IllegalSectionName("section name cannot contain a '@' unless it indicates a private section") |
199 | | - |
200 | | - priv, sid = True, sid[1:] |
201 | | - |
202 | | - dt["sections"][ns]["lines"] = dt["code"] |
203 | | - dt["sections"][f"{fileid}.{sid}"] = { |
204 | | - "file": filename, "start": lno + 1, "lines": [], "priv": priv, |
205 | | - "args": line.split(" ")[1:], "ret": None, "as": fileid, "path": filepath |
206 | | - } |
207 | | - dt["code"] = [] |
208 | | - dt["active"] = sid |
209 | | - continue |
210 | | - |
211 | | - dt["code"].append(line.lstrip()) |
212 | | - |
213 | | - if dt["code"]: |
214 | | - dt["sections"][f"{fileid}.{dt['active']}"]["lines"] = dt["code"] |
215 | | - |
216 | | - self.sections = {**self.sections, **dt["sections"]} |
217 | | - if external: |
218 | | - self.run_section(f"{fileid}.global") |
219 | | - del self.sections[f"{fileid}.global"] # Save memory |
220 | | - |
221 | | - def find_section(self, section: str) -> Tuple[str, str]: |
222 | | - current_file = (self.linetrk[-1]["path"] if self.linetrk else self._entrypoint).removesuffix(".xt").replace("/", ".") |
223 | | - if "." not in section: |
224 | | - section = f"{current_file}.{section}" |
225 | | - |
226 | | - if section not in self.sections: |
227 | | - raise InvalidSection(section) |
228 | | - |
229 | | - return section, current_file |
230 | | - |
231 | | - def run_section(self, section: str) -> Any: |
232 | | - section, current_file = self.find_section(section) |
233 | | - secdata = section.split(".") |
234 | | - s = self.sections[section] |
235 | | - if s["priv"] and current_file + ".xt" != s["file"]: |
236 | | - raise InvalidSection(f"{secdata[1]} is a private section and cannot be called") |
237 | | - |
238 | | - if section not in self.memory.vars["local"]: |
239 | | - self.memory.vars["local"][section] = {} |
240 | | - |
241 | | - self.linetrk.append({"file": s["file"], "path": s["path"], "section": section, "start": s["start"], "ended": False, "as": s["as"]}) |
242 | | - for line in s["lines"]: |
243 | | - self.linetrk[-1]["start"] += 1 |
244 | | - if line.strip() and line[:2] != "::": |
245 | | - self.execute(line) |
246 | | - if self.linetrk[-1]['ended']: |
247 | | - break |
248 | | - |
249 | | - del self.memory.vars["local"][section] |
250 | | - |
251 | | - self.linetrk.pop() |
252 | | - return s["ret"] |
253 | | - |
254 | | -# Handler |
255 | | -inter = XTInterpreter(opmap) |
256 | | -if not sys.argv: |
257 | | - print(f"{__version__} Copyright (c) 2022 iiPython") |
258 | | - inter._live, linedata = True, "" |
259 | | - inter.load_sections(":global\n", "<stdin>") |
260 | | - inter.memory.vars["local"]["<stdin>.global"] = {} |
261 | | - while True: |
262 | | - try: |
263 | | - line = input(f"{'>' if not linedata else ':'} ") |
264 | | - if line[:2] == "::": |
265 | | - continue |
266 | | - |
267 | | - elif linedata and not line.strip(): |
268 | | - inter.load_sections(linedata, "<stdin>") |
269 | | - linedata = "" |
270 | | - |
271 | | - elif not line.strip(): |
272 | | - continue |
273 | | - |
274 | | - elif line[0] == ":" and line[:2] != "::": |
275 | | - linedata = line + "\n" |
276 | | - |
277 | | - elif linedata: |
278 | | - linedata += line + "\n" |
279 | | - |
280 | | - else: |
281 | | - inter.execute(line) |
282 | | - |
283 | | - except KeyboardInterrupt: |
284 | | - os._exit(0) |
285 | | - |
286 | | -else: |
287 | | - file = sys.argv[0] |
288 | | - if file == ".": |
289 | | - file = config.get("main", "main.xt") |
290 | | - |
291 | | - try: |
292 | | - with open(file, "r", encoding = "utf-8") as f: |
293 | | - code = f.read() |
294 | | - |
295 | | - except Exception: |
296 | | - print("x2: failed to load file") |
297 | | - os._exit(1) |
298 | | - |
299 | | - inter.load_sections(code, file) |
300 | | - file = file.replace("\\", "/").replace("/", ".").removesuffix(".xt").removeprefix("./") |
301 | | - [inter.run_section(s) for s in [f"{file}.global", f"{file}.main"]] |
| 29 | +# Run file |
| 30 | +sections = load_sections(data, filepath) |
| 31 | +interpreter = Interpreter( |
| 32 | + filepath, sections, |
| 33 | + config = config, |
| 34 | + cli_vals = cli.vals |
| 35 | +) |
| 36 | +interpreter.run_section("main") |
0 commit comments