"""Validation of the expression/statement AST accepted by the web interpreter.""" def expression(value, depth=0): if depth > 64 or not isinstance(value, dict): raise ValueError("Некорректное PGVN-выражение") kind = value.get("k") def ex(key, optional=False): v = value.get(key) if not optional or v is not None: expression(v, depth + 1) def seq(key): v = value.get(key) if not isinstance(v, list): raise ValueError(f"Выражение {kind}: нужен список {key}") return v if kind == "nani": nani_expression(value.get("ast"), depth + 1) elif kind == "const": if "v" not in value: raise ValueError("const без значения") elif kind == "name": if not isinstance(value.get("id"), str): raise ValueError("Имя переменной должно быть строкой") elif kind == "attr": ex("o") if not isinstance(value.get("a"), str): raise ValueError("Атрибут должен быть строкой") elif kind in ("sub", "slice"): ex("o") if kind == "sub": ex("i") else: for key in ("lo", "hi", "st"): ex(key, True) elif kind in ("bin", "unary", "bool"): from app.converter.pyexpr import BIN_OPS, UNARY_OPS allowed = set(BIN_OPS.values()) if kind == "bin" else set(UNARY_OPS.values()) if kind == "unary" else {"and", "or"} if value.get("op") not in allowed: raise ValueError("Неизвестная операция выражения") if kind == "bin": ex("l") ex("r") elif kind == "unary": ex("v") else: for item in seq("v"): expression(item, depth + 1) elif kind == "cmp": from app.converter.pyexpr import CMP_OPS ex("l") operators, operands = seq("ops"), seq("r") if len(operators) != len(operands) or any(op not in CMP_OPS.values() for op in operators): raise ValueError("Некорректное сравнение") for item in operands: expression(item, depth + 1) elif kind == "if": for key in ("c", "t", "e"): ex(key) elif kind in ("list", "tuple", "fmt", "dict"): for item in seq("v"): if kind == "dict": if not isinstance(item, list) or len(item) != 2: raise ValueError("Некорректная запись словаря") for part in item: expression(part, depth + 1) elif kind != "fmt" or not isinstance(item, str): expression(item, depth + 1) elif kind == "star": ex("v") elif kind == "yield": ex("v", optional=True) elif kind == "lambda": parameters(value.get("params"), depth + 1) ex("body") elif kind == "call": ex("f") for item in seq("a"): expression(item, depth + 1) for item in value.get("ks", []): expression(item, depth + 1) if not isinstance(value.get("kw"), dict): raise ValueError("Некорректные аргументы вызова") for item in value["kw"].values(): expression(item, depth + 1) elif kind == "comp": ex("e") ex("v", True) if "gens" in value: gens = seq("gens") if not gens or len(gens) > 8: raise ValueError("Некорректный генератор") for gen in gens: if not isinstance(gen, dict) or not isinstance(gen.get("if"), list): raise ValueError("Некорректный генератор") expression(gen.get("t"), depth + 1) expression(gen.get("it"), depth + 1) for item in gen["if"]: expression(item, depth + 1) else: # saved by an older converter for key in ("t", "it"): ex(key) for item in seq("if"): expression(item, depth + 1) else: raise ValueError(f"Неизвестное выражение: {kind}") def parameters(params, depth): if not isinstance(params, list) or len(params) > 64: raise ValueError("Некорректные параметры функции") for p in params: if not isinstance(p, dict) or not isinstance(p.get("n"), str) or p.get("kind") not in ("p", "k", "*", "**"): raise ValueError("Некорректный параметр функции") if p.get("d") is not None: expression(p["d"], depth + 1) def statements(code, depth=0): if depth > 64 or not isinstance(code, list): raise ValueError("Некорректный PGVN AST") for s in code: if not isinstance(s, dict): raise ValueError("Некорректный оператор AST") op = s.get("op") keys = {"set": ("t", "v"), "aug": ("t", "v"), "eval": ("e",), "if": ("c",), "while": ("c",), "for": ("t", "it"), "del": ("t",), "break": (), "continue": (), "def": (), "class": (), "return": (), "global": (), "nonlocal": (), "try": (), "raise": (), "yield": (), "yield_from": ("v",), "in_store": (), "assert": ("c",)} if op not in keys: raise ValueError(f"Неизвестный оператор AST: {op}") for key in keys[op]: expression(s.get(key), depth + 1) if op == "in_store": if not isinstance(s.get("ns"), str) or not all(part.isidentifier() for part in s["ns"].split(".")): raise ValueError("Некорректное имя хранилища") statements(s.get("body"), depth + 1) if op == "if": statements(s.get("then"), depth + 1) statements(s.get("else"), depth + 1) if op in ("while", "for"): statements(s.get("body"), depth + 1) statements(s.get("else", []), depth + 1) if op == "try": statements(s.get("body"), depth + 1) statements(s.get("else", []), depth + 1) statements(s.get("final", []), depth + 1) handlers = s.get("handlers") if not isinstance(handlers, list) or len(handlers) > 32: raise ValueError("Некорректные обработчики исключений") for h in handlers: if not isinstance(h, dict) or not (h.get("name") is None or isinstance(h.get("name"), str)): raise ValueError("Некорректный обработчик исключения") if h.get("types") is not None: expression(h["types"], depth + 1) statements(h.get("body"), depth + 1) if op == "raise" and s.get("v") is not None: expression(s["v"], depth + 1) if op == "assert" and s.get("m") is not None: expression(s["m"], depth + 1) if op == "def": if not isinstance(s.get("name"), str): raise ValueError("У функции нет имени") parameters(s.get("params"), depth + 1) statements(s.get("body"), depth + 1) for item in s.get("decos", []): expression(item, depth + 1) if op == "class": if not isinstance(s.get("name"), str): raise ValueError("У класса нет имени") for item in s.get("bases", []): expression(item, depth + 1) statements(s.get("body"), depth + 1) if op == "return" and s.get("v") is not None: expression(s["v"], depth + 1) if op in ("global", "nonlocal") and not ( isinstance(s.get("names"), list) and all(isinstance(n, str) for n in s["names"])): raise ValueError("Некорректный список имён") def nani_expression(node, depth=0): """Validate the optional Naninovel AST already supported by the shared player.""" if depth > 64 or not isinstance(node, list) or not node: raise ValueError("Некорректный AST Naninovel") kind = node[0] if kind == "const" and len(node) == 2 and (node[1] is None or isinstance(node[1], (str, int, float, bool))): return if kind == "name" and len(node) == 2 and isinstance(node[1], str): return children = None if kind == "unary" and len(node) == 3 and node[1] in ("!", "not", "+", "-", "~"): children = node[2:] elif kind == "bin" and len(node) == 4: from app.converter.nani_expr import PRECEDENCE if node[1] in PRECEDENCE: children = node[2:] elif kind == "if" and len(node) == 4: children = node[1:] elif kind == "call" and len(node) == 3: from app.converter.nani_expr import FUNCTIONS if node[1] in FUNCTIONS and isinstance(node[2], list): children = node[2] if children is None: raise ValueError("Неизвестная операция AST Naninovel") for child in children: nani_expression(child, depth + 1)