"""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 == "call": ex("f") for item in seq("a"): 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": for key in ("e", "t", "it"): ex(key) for item in seq("if"): expression(item, depth + 1) else: raise ValueError(f"Неизвестное выражение: {kind}") 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": ()} if op not in keys: raise ValueError(f"Неизвестный оператор AST: {op}") for key in keys[op]: expression(s.get(key), 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) 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)