| | |
| | |
| | |
| | |
| | |
| |
|
| | import type {Parser, Node as TreeSitterNode, Tree} from "web-tree-sitter";
|
| |
|
| | import {check, deepFreeze} from "./shared_utils.js";
|
| |
|
| |
|
| | const MEMOIZED = {parser: null as unknown as Parser};
|
| |
|
| | interface Dict extends Object {
|
| | [k: string]: unknown;
|
| | }
|
| | interface ExecutionContextData extends Object {
|
| | [k: string]: unknown;
|
| | }
|
| | class ExecuteContext implements ExecutionContextData {
|
| | [k: string]: unknown;
|
| |
|
| | constructor(existing: Object = {}) {
|
| | Object.assign(this, !!window.structuredClone ? structuredClone(existing) : {...existing});
|
| | }
|
| | }
|
| | class InitialExecuteContext extends ExecuteContext {}
|
| |
|
| | type NodeHandlerArgs = [ExecutionContextData, BuiltInFns];
|
| | type NodeHandler = (node: Node, ...args: NodeHandlerArgs) => Promise<any>;
|
| |
|
| | const TYPE_TO_HANDLER = new Map<string, NodeHandler>([
|
| | ["module", handleChildren],
|
| | ["expression_statement", handleChildren],
|
| | ["interpolation", handleInterpolation],
|
| | ["block", handleChildren],
|
| |
|
| | ["comment", handleSwallow],
|
| | ["return_statement", handleReturn],
|
| |
|
| | ["assignment", handleAssignment],
|
| | ["named_expression", handleNamedExpression],
|
| |
|
| | ["identifier", handleIdentifier],
|
| | ["attribute", handleAttribute],
|
| | ["subscript", handleSubscript],
|
| |
|
| | ["call", handleCall],
|
| | ["argument_list", handleArgumentsList],
|
| |
|
| | ["for_statement", handleForStatement],
|
| | ["list_comprehension", handleListComprehension],
|
| |
|
| | ["comparison_operator", handleComparisonOperator],
|
| | ["boolean_operator", handleBooleanOperator],
|
| | ["binary_operator", handleBinaryOperator],
|
| | ["not_operator", handleNotOperator],
|
| | ["unary_operator", handleUnaryOperator],
|
| |
|
| |
|
| | ["integer", handleNumber],
|
| | ["float", handleNumber],
|
| | ["string", handleString],
|
| | ["tuple", handleList],
|
| | ["list", handleList],
|
| | ["dictionary", handleDictionary],
|
| | ["pair", handleDictionaryPair],
|
| | ["true", async (...args: any[]) => true],
|
| | ["false", async (...args: any[]) => false],
|
| | ]);
|
| |
|
| | type BuiltInFn = {fn: Function};
|
| | type BuiltInFns = {[key: string]: BuiltInFn};
|
| |
|
| | const DEFAULT_BUILT_INS: BuiltInFns = {
|
| | round: {fn: (n: any) => Math.round(Number(n))},
|
| | ceil: {fn: (n: any) => Math.ceil(Number(n))},
|
| | floor: {fn: (n: any) => Math.floor(Number(n))},
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | len: {fn: (n: any) => n?.__len__?.() ?? n?.length},
|
| |
|
| |
|
| |
|
| |
|
| | int: {fn: (n: any) => Math.floor(Number(n))},
|
| | float: {fn: (n: any) => Number(n)},
|
| | str: {fn: (n: any) => String(n)},
|
| | bool: {fn: (n: any) => !!n},
|
| | list: {fn: (tupl: any[] = []) => new PyList(tupl)},
|
| | tuple: {fn: (list: any[] = []) => new PyTuple(list)},
|
| | dict: {fn: (dict: Dict = {}) => new PyDict(dict)},
|
| |
|
| |
|
| | dir: {fn: (...args: any[]) => console.dir(...__unwrap__(...args))},
|
| | print: {fn: (...args: any[]) => console.log(...__unwrap__(...args))},
|
| | log: {fn: (...args: any[]) => console.log(...__unwrap__(...args))},
|
| | };
|
| |
|
| | |
| | |
| |
|
| | export async function execute(
|
| | code: string,
|
| | ctx: ExecutionContextData,
|
| | additionalBuiltins?: BuiltInFns,
|
| | ) {
|
| | const builtIns = deepFreeze({...DEFAULT_BUILT_INS, ...(additionalBuiltins ?? {})});
|
| |
|
| |
|
| |
|
| | ctx = new InitialExecuteContext(ctx);
|
| |
|
| | const root = (await parse(code)).rootNode;
|
| | const value = await handleNode(new Node(root), ctx, builtIns);
|
| |
|
| | console.log("=====");
|
| | console.log(`value`, value?.__unwrap__?.() ?? value);
|
| | console.log("context", ctx);
|
| |
|
| | return value;
|
| | }
|
| |
|
| | |
| | |
| |
|
| | async function parse(code: string): Promise<Tree> {
|
| | if (!MEMOIZED.parser) {
|
| |
|
| | const TreeSitter = (await import("rgthree/lib/tree-sitter.js")) as TreeSitter;
|
| | await TreeSitter.Parser.init();
|
| | const lang = await TreeSitter.Language.load("rgthree/lib/tree-sitter-python.wasm");
|
| | MEMOIZED.parser = new TreeSitter.Parser() as Parser;
|
| | MEMOIZED.parser.setLanguage(lang);
|
| | }
|
| | return MEMOIZED.parser.parse(code)!;
|
| | }
|
| |
|
| | |
| | |
| | |
| |
|
| | async function handleNode(
|
| | node: Node,
|
| | ctx: ExecutionContextData,
|
| | builtIns: BuiltInFns,
|
| | ): Promise<any> {
|
| | const type = node.type as string;
|
| |
|
| |
|
| | if (ctx.hasOwnProperty("__returned__")) return ctx["__returned__"];
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | const handler = TYPE_TO_HANDLER.get(type);
|
| | check(handler, "Unhandled type: " + type, node);
|
| | return handler(node, ctx, builtIns);
|
| | }
|
| |
|
| | |
| | |
| |
|
| | async function handleChildren(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| | let lastValue = null;
|
| | for (const child of node.children) {
|
| | if (!child) continue;
|
| | lastValue = await handleNode(child, ctx, builtIns);
|
| | }
|
| | return lastValue;
|
| | }
|
| |
|
| | |
| | |
| |
|
| | async function handleSwallow(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| |
|
| | }
|
| |
|
| | |
| | |
| |
|
| | async function handleReturn(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| | const value = node.children.length > 1 ? handleNode(node.child(1), ctx, builtIns) : undefined;
|
| |
|
| |
|
| | ctx["__returned__"] = value;
|
| | return value;
|
| | }
|
| |
|
| | |
| | |
| |
|
| | async function handleIdentifier(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| | let value = ctx[node.text];
|
| | if (value === undefined) {
|
| | value = builtIns[node.text]?.fn ?? undefined;
|
| | }
|
| | return maybeWrapValue(value);
|
| | }
|
| |
|
| | async function handleAttribute(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| | const children = node.children;
|
| | check(children.length === 3, "Expected 3 children for attribute.");
|
| | check(children[1]!.type === ".", "Expected middle child to be '.' for attribute.");
|
| | const inst = await handleNode(children[0]!, ctx, builtIns);
|
| |
|
| |
|
| | const attr = children[2]!.text;
|
| | checkAttributeAccessibility(inst, attr);
|
| | let attribute = maybeWrapValue(inst[attr]);
|
| |
|
| |
|
| | return typeof attribute === "function" ? attribute.bind(inst) : attribute;
|
| | }
|
| |
|
| | async function handleSubscript(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| | const children = node.children;
|
| | check(children.length === 4, "Expected 4 children for subscript.");
|
| | check(children[1]!.type === "[", "Expected 2nd child to be '[' for subscript.");
|
| | check(children[3]!.type === "]", "Expected 4thd child to be ']' for subscript.");
|
| | const inst = await handleNode(children[0]!, ctx, builtIns);
|
| | const attr = await handleNode(children[2]!, ctx, builtIns);
|
| | if (inst instanceof PyTuple && isInt(attr)) {
|
| | return maybeWrapValue(inst.__at__(attr));
|
| | }
|
| | if (inst instanceof PyDict && typeof attr === "string") {
|
| | return maybeWrapValue(inst.get(attr));
|
| | }
|
| | checkAttributeAccessibility(inst, attr);
|
| | let attribute = maybeWrapValue(inst[attr]);
|
| | return typeof attribute === "function" ? attribute.bind(inst) : attribute;
|
| | }
|
| |
|
| | |
| | |
| |
|
| | async function handleAssignment(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| | check(
|
| | node.children.length === 3,
|
| | "Expected 3 children for assignment: identifier/attr, =, and value.",
|
| | );
|
| | check(node.children[1]!.type === "=", "Expected middle child to be an '='.");
|
| |
|
| | let right = await handleNode(node.children[2]!, ctx, builtIns);
|
| | const leftNode = node.children[0]!;
|
| | let leftObj: any = ctx;
|
| | let leftProp: string | number = "";
|
| | if (leftNode.type === "identifier") {
|
| | leftProp = leftNode.text;
|
| | } else if (leftNode.type === "attribute") {
|
| | leftObj = await handleNode(leftNode.children[0]!, ctx, builtIns);
|
| | check(
|
| | leftNode.children[2]!.type === "identifier",
|
| | "Expected left hand assignment attribute to be an identifier.",
|
| | leftNode,
|
| | );
|
| | leftProp = leftNode.children[2]!.text;
|
| | } else if (leftNode.type === "subscript") {
|
| | leftObj = await handleNode(leftNode.children[0]!, ctx, builtIns);
|
| | check(leftNode.children[1]!.type === "[");
|
| | check(leftNode.children[3]!.type === "]");
|
| | leftProp = await handleNode(leftNode.children[2]!, ctx, builtIns);
|
| | } else {
|
| | throw new Error(`Unhandled left-hand assignement type: ${leftNode.type}`);
|
| | }
|
| |
|
| | if (leftProp == null) {
|
| | throw new Error(`No property to assign value`);
|
| | }
|
| |
|
| |
|
| | if (leftObj instanceof PyTuple) {
|
| | check(isInt(leftProp), "Expected an int for list assignment");
|
| | leftObj.__put__(leftProp, right);
|
| | } else if (leftObj instanceof PyDict) {
|
| | check(typeof leftProp === "string", "Expected a string for dict assignment");
|
| | leftObj.__put__(leftProp, right);
|
| | } else {
|
| | check(typeof leftProp === "string", "Expected a string for object assignment");
|
| |
|
| |
|
| | if (!(leftObj instanceof InitialExecuteContext)) {
|
| | checkAttributeAccessibility(leftObj, leftProp);
|
| | }
|
| | leftObj[leftProp] = right;
|
| | }
|
| | return right;
|
| | }
|
| |
|
| | |
| | |
| | |
| |
|
| | async function handleNamedExpression(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| | check(node.children.length === 3, "Expected three children for named expression.");
|
| | check(node.child(0).type === "identifier", "Expected identifier first in named expression.");
|
| | const varName = node.child(0).text;
|
| | ctx[varName] = await handleNode(node.child(2), ctx, builtIns);
|
| | return maybeWrapValue(ctx[varName]);
|
| | }
|
| |
|
| | |
| | |
| |
|
| | async function handleCall(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| | check(node.children.length === 2, "Expected 2 children for call, identifier and arguments.");
|
| | const fn = await handleNode(node.children[0]!, ctx, builtIns);
|
| | const args = await handleNode(node.children[1]!, ctx, builtIns);
|
| | console.log("handleCall", fn, args);
|
| | return fn(...args);
|
| | }
|
| |
|
| | async function handleArgumentsList(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| | const args = (await handleList(node, ctx, builtIns)).__unwrap__(false);
|
| | return [...args];
|
| | }
|
| |
|
| | |
| | |
| |
|
| | async function handleForStatement(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| | const childs = node.children;
|
| | check(childs.length === 6);
|
| | check(childs[4]!.type === ":");
|
| | check(childs[5]!.type === "block");
|
| | await helperGetLoopForIn(node, ctx, builtIns, async (forCtx) => {
|
| | await handleNode(childs[5]!, forCtx, builtIns);
|
| | });
|
| | }
|
| |
|
| | async function handleListComprehension(
|
| | node: Node,
|
| | ctx: ExecutionContextData,
|
| | builtIns: BuiltInFns,
|
| | ) {
|
| |
|
| | const finalList = new PyList();
|
| | const newCtx = {...ctx};
|
| |
|
| | let finalEntryNode;
|
| | const loopNodes: {forIn: Node; if?: Node}[] = [];
|
| |
|
| | for (const child of node.children) {
|
| | if (!child || ["[", "]"].includes(child.type)) continue;
|
| | if (child.type === "identifier" || child.type === "attribute") {
|
| | if (finalEntryNode) {
|
| | throw Error("Already have a list comprehension finalEntryNode.");
|
| | }
|
| | finalEntryNode = child;
|
| | } else if (child.type === "for_in_clause") {
|
| | loopNodes.push({forIn: child});
|
| | } else if (child.type === "if_clause") {
|
| | loopNodes[loopNodes.length - 1]!["if"] = child;
|
| | }
|
| | }
|
| | if (!finalEntryNode) {
|
| | throw Error("No list comprehension finalEntryNode.");
|
| | }
|
| |
|
| | console.log(`handleListComprehension.loopNodes`, loopNodes);
|
| |
|
| | const handleLoop = async (loopNodes: {forIn: Node; if?: Node}[]) => {
|
| | const loopNode = loopNodes.shift()!;
|
| | await helperGetLoopForIn(
|
| | loopNode.forIn,
|
| | newCtx,
|
| | builtIns,
|
| | async (forCtx) => {
|
| | if (loopNode.if) {
|
| | const ifNode = loopNode.if;
|
| | check(ifNode.children.length === 2, "Expected 2 children for if_clause.");
|
| | check(ifNode.child(0).text === "if", "Expected first child to be 'if'.");
|
| | const good = await handleNode(ifNode.child(1), forCtx, builtIns);
|
| | if (!good) return;
|
| | }
|
| | Object.assign(newCtx, forCtx);
|
| | if (loopNodes.length) {
|
| | await handleLoop(loopNodes);
|
| | } else {
|
| | finalList.append(await handleNode(finalEntryNode, newCtx, builtIns));
|
| | }
|
| | },
|
| | () => ({...newCtx}),
|
| | );
|
| | loopNodes.unshift(loopNode);
|
| | };
|
| |
|
| | await handleLoop(loopNodes);
|
| | return finalList;
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | async function helperGetLoopForIn(
|
| | node: Node,
|
| | ctx: ExecutionContextData,
|
| | builtIns: BuiltInFns,
|
| | eachFn: (forCtx: ExecutionContextData) => Promise<void>,
|
| | provideForCtx?: () => ExecutionContextData,
|
| | ) {
|
| | const childs = node.children;
|
| | check(childs.length >= 3);
|
| | check(childs[0]!.type === "for");
|
| | check(
|
| | ["identifier", "pattern_list"].includes(childs[1]!.type),
|
| | "Expected identifier for for loop.",
|
| | );
|
| | check(childs[2]!.type === "in");
|
| |
|
| | let identifiers: string[];
|
| | if (childs[1]!.type === "identifier") {
|
| |
|
| | identifiers = [childs[1]!.text];
|
| | } else {
|
| |
|
| | identifiers = childs[1]!.children
|
| | .map((n) => {
|
| | if (n.type === ",") return null;
|
| | check(n.type === "identifier");
|
| | return node.text;
|
| | })
|
| | .filter((n) => n != null);
|
| | }
|
| | const iterable = await handleNode(childs[3]!, ctx, builtIns);
|
| | check(iterable instanceof PyTuple, "Expected for loop instance to be a list/tuple.");
|
| |
|
| | for (const item of iterable.__unwrap__(false)) {
|
| | const forCtx = provideForCtx?.() ?? ctx;
|
| | if (identifiers.length === 1) {
|
| | forCtx[identifiers[0]!] = item;
|
| | } else {
|
| | check(
|
| | Array.isArray(item) && identifiers.length === item.length,
|
| | "Expected iterable to be a list, like using dict.items()",
|
| | );
|
| | for (let i = 0; i < identifiers.length; i++) {
|
| | forCtx[identifiers[i]!] = item[i];
|
| | }
|
| | }
|
| | await eachFn(forCtx);
|
| | }
|
| | }
|
| |
|
| | async function handleNumber(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| | return Number(node.text);
|
| | }
|
| |
|
| | async function handleString(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| |
|
| | let str = "";
|
| | for (const child of node.children) {
|
| | if (!child || ["string_start", "string_end"].includes(child.type)) continue;
|
| | if (child.type === "string_content") {
|
| | str += child.text;
|
| | } else if (child.type === "interpolation") {
|
| | check(child.children.length === 3, "Expected interpolation");
|
| | str += await handleNode(child, ctx, builtIns);
|
| | }
|
| | }
|
| | return str;
|
| | }
|
| |
|
| | async function handleInterpolation(node: Node, ...args: NodeHandlerArgs) {
|
| | check(node.children.length === 3, "Expected interpolation to be three nodes length.");
|
| | check(
|
| | node.children[0]!.type === "{" && node.children[2]!.type === "}",
|
| | 'Expected interpolation to be wrapped in "{" and "}".',
|
| | );
|
| | return await handleNode(node.children[1]!, ...args);
|
| | }
|
| |
|
| | async function handleList(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| | const list = [];
|
| | for (const child of node.children) {
|
| | if (!child || ["(", "[", ",", "]", ")"].includes(child.type)) continue;
|
| | list.push(await handleNode(child, ctx, builtIns));
|
| | }
|
| | if (node.type === "tuple") {
|
| | return new PyTuple(list);
|
| | }
|
| | return new PyList(list);
|
| | }
|
| |
|
| | async function handleComparisonOperator(
|
| | node: Node,
|
| | ctx: ExecutionContextData,
|
| | builtIns: BuiltInFns,
|
| | ) {
|
| | const op = node.child(1).text;
|
| | const left = await handleNode(node.child(0), ctx, builtIns);
|
| | const right = await handleNode(node.child(2), ctx, builtIns);
|
| | if (op === "==") return left === right;
|
| | if (op === "!=") return left !== right;
|
| | if (op === ">") return left > right;
|
| | if (op === ">=") return left >= right;
|
| | if (op === "<") return left < right;
|
| | if (op === "<=") return left <= right;
|
| | if (op === "in") return (right.__unwrap__ ? right.__unwrap__(false) : right).includes(left);
|
| | throw new Error(`Comparison not handled: "${op}"`);
|
| | }
|
| | async function handleBooleanOperator(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| | const op = node.child(1).text;
|
| | const left = await handleNode(node.child(0), ctx, builtIns);
|
| |
|
| | if (!left && op === "and") return left;
|
| | const right = await handleNode(node.child(2), ctx, builtIns);
|
| | if (op === "and") return left && right;
|
| | if (op === "or") return left || right;
|
| | }
|
| |
|
| | async function handleBinaryOperator(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| | const op = node.child(1).text;
|
| | const left = await handleNode(node.child(0), ctx, builtIns);
|
| | const right = await handleNode(node.child(2), ctx, builtIns);
|
| | if (left.constructor !== right.constructor) {
|
| | throw new Error(`Can only run ${op} operator on same type.`);
|
| | }
|
| | if (op === "+") return left.__add__ ? left.__add__(right) : left + right;
|
| | if (op === "-") return left - right;
|
| | if (op === "/") return left / right;
|
| | if (op === "//") return Math.floor(left / right);
|
| | if (op === "*") return left * right;
|
| | if (op === "%") return left % right;
|
| | if (op === "&") return left & right;
|
| | if (op === "|") return left | right;
|
| | if (op === "^") return left ^ right;
|
| | if (op === "<<") return left << right;
|
| | if (op === ">>") return left >> right;
|
| | throw new Error(`Comparison not handled: "${op}"`);
|
| | }
|
| |
|
| | async function handleNotOperator(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| | check(node.children.length === 2, "Expected 2 children for not operator.");
|
| | check(node.child(0).text === "not", "Expected first child to be 'not'.");
|
| | const value = await handleNode(node.child(1), ctx, builtIns);
|
| | return !value;
|
| | }
|
| |
|
| | async function handleUnaryOperator(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| | check(node.children.length === 2, "Expected 2 children for not operator.");
|
| | const value = await handleNode(node.child(1), ctx, builtIns);
|
| | const op = node.child(0).text;
|
| | if (op === "-") return value * -1;
|
| | console.warn(`Unhandled unary operator: ${op}`);
|
| | return value;
|
| | }
|
| |
|
| | async function handleDictionary(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| | const dict = new PyDict();
|
| | for (const child of node.children) {
|
| | if (!child || ["{", ",", "}"].includes(child.type)) continue;
|
| | check(child.type === "pair", "Expected a pair type for dict.");
|
| | const pair = await handleNode(child, ctx, builtIns);
|
| | dict.__put__(pair[0], pair[1]);
|
| | }
|
| | return dict;
|
| | }
|
| |
|
| | async function handleDictionaryPair(node: Node, ctx: ExecutionContextData, builtIns: BuiltInFns) {
|
| | check(node.children.length === 3, "Expected 3 children for dict pair.");
|
| | let varName = await handleNode(node.child(0)!, ctx, builtIns);
|
| | let varValue = await handleNode(node.child(2)!, ctx, builtIns);
|
| | check(typeof varName === "string", "Expected varname to be string.");
|
| | return [varName, varValue];
|
| | }
|
| |
|
| | |
| | |
| |
|
| | class Node {
|
| | type: string;
|
| | text: string;
|
| | children: Node[];
|
| | private node: TreeSitterNode;
|
| |
|
| | constructor(node: TreeSitterNode) {
|
| | this.type = node.type;
|
| | this.text = node.text;
|
| | if (this.type === "ERROR") {
|
| | throw new Error(`Error found in parsing near "${this.text}"`);
|
| | }
|
| | this.children = [];
|
| | for (const child of node.children) {
|
| | this.children.push(new Node(child!));
|
| | }
|
| | this.node = node;
|
| | }
|
| |
|
| | child(index: number): Node {
|
| | const child = this.children[index];
|
| | if (!child) throw Error(`No child at index ${index}.`);
|
| | return child;
|
| | }
|
| |
|
| | log(tab = "", showNode = false) {
|
| | console.log(`${tab}--- Node`);
|
| | console.log(`${tab} type: ${this.type}`);
|
| | console.log(`${tab} text: ${this.text}`);
|
| | console.log(`${tab} children:`, this.children);
|
| | if (showNode) {
|
| | console.log(`${tab} node:`, this.node);
|
| | }
|
| | }
|
| | }
|
| |
|
| | |
| | |
| |
|
| | export class PyTuple {
|
| | protected list: any[];
|
| | constructor(...args: any[]) {
|
| | if (args.length === 1 && args[0] instanceof PyTuple) {
|
| | args = args[0].__unwrap__(false);
|
| | }
|
| | if (args.length === 1 && Array.isArray(args[0])) {
|
| | args = [...args[0]];
|
| | }
|
| | this.list = [...args];
|
| | }
|
| |
|
| | @Exposed count(v: any) {
|
| |
|
| | }
|
| |
|
| | @Exposed index() {
|
| |
|
| | }
|
| |
|
| | __at__(index: number) {
|
| | index = this.__get_relative_index__(index);
|
| | return this.list[index];
|
| | }
|
| |
|
| | __len__() {
|
| | return this.list.length;
|
| | }
|
| |
|
| | __add__(v: any) {
|
| | if (!(v instanceof PyTuple)) {
|
| | throw new Error("Can only concatenate tuple to tuple.");
|
| | }
|
| | return new PyTuple(this.__unwrap__(false).concat(v.__unwrap__(false)));
|
| | }
|
| |
|
| |
|
| | __put__(index: number, v: any) {
|
| | throw new Error("Tuple does not support item assignment");
|
| | }
|
| |
|
| |
|
| | protected __get_relative_index__(index: number) {
|
| | if (index >= 0) {
|
| | check(this.list.length > index, `Index ${index} out of range.`);
|
| | return index;
|
| | }
|
| | const relIndex = this.list.length + index;
|
| | check(relIndex >= 0, `Index ${index} out of range.`);
|
| | return relIndex;
|
| | }
|
| |
|
| | |
| | |
| |
|
| | __unwrap__(deep = true) {
|
| | const l = [...this.list];
|
| | if (deep) {
|
| | for (let i = 0; i < l.length; i++) {
|
| | l[i] = l[i]?.__unwrap__ ? l[i].__unwrap__(deep) : l[i];
|
| | }
|
| | }
|
| | return l;
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | }
|
| |
|
| | |
| | |
| |
|
| | export class PyList extends PyTuple {
|
| | @Exposed append(...args: any[]) {
|
| | this.list.push(...args);
|
| | }
|
| |
|
| | @Exposed clear() {
|
| | this.list.length = 0;
|
| | }
|
| |
|
| | @Exposed copy() {
|
| |
|
| | }
|
| |
|
| | @Exposed override count() {
|
| |
|
| | }
|
| | @Exposed extend() {
|
| |
|
| | }
|
| | @Exposed override index() {
|
| |
|
| | }
|
| | @Exposed insert() {
|
| |
|
| | }
|
| | @Exposed pop() {
|
| |
|
| | }
|
| | @Exposed remove() {
|
| |
|
| | }
|
| | @Exposed reverse() {
|
| |
|
| | }
|
| | @Exposed sort() {
|
| |
|
| | }
|
| |
|
| | override __add__(v: any) {
|
| | if (!(v instanceof PyList)) {
|
| | throw new Error("Can only concatenate list to list.");
|
| | }
|
| | return new PyList(this.__unwrap__(false).concat(v.__unwrap__(false)));
|
| | }
|
| |
|
| |
|
| | override __put__(index: number, v: any) {
|
| | index = this.__get_relative_index__(index);
|
| | this.list[index] = v;
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | }
|
| |
|
| | class PyInt {}
|
| |
|
| | class PyDict {
|
| | #dict: {[key: string]: any};
|
| | constructor(dict?: {[key: string]: any}) {
|
| | this.#dict = {...(dict ?? {})};
|
| | }
|
| |
|
| | @Exposed clear() {}
|
| | @Exposed copy() {}
|
| | @Exposed fromkeys() {}
|
| |
|
| | @Exposed get(key: string) {
|
| | return this.#dict[key];
|
| | }
|
| |
|
| | @Exposed items() {
|
| | return new PyTuple(Object.entries(this.#dict).map((e) => new PyTuple(e)));
|
| | }
|
| | @Exposed keys() {}
|
| | @Exposed pop() {}
|
| | @Exposed popitem() {}
|
| | @Exposed setdefault() {}
|
| | @Exposed update() {}
|
| | @Exposed values() {}
|
| |
|
| | __put__(key: string, v: any) {
|
| | this.#dict[key] = v;
|
| | }
|
| |
|
| | __len__() {
|
| | return Object.keys(this.#dict).length;
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | |
| | |
| |
|
| | __unwrap__(deep = true) {
|
| | const d = {...this.#dict};
|
| | if (deep) {
|
| | for (let k of Object.keys(d)) {
|
| | d[k] = d[k]?.__unwrap__ ? d[k].__unwrap__(deep) : d[k];
|
| | }
|
| | }
|
| | return d;
|
| | }
|
| | }
|
| |
|
| | |
| | |
| |
|
| | function __unwrap__(...args: any[]) {
|
| | for (let i = 0; i < args.length; i++) {
|
| | args[i] = args[i]?.__unwrap__ ? args[i].__unwrap__(true) : args[i];
|
| | }
|
| | return args;
|
| | }
|
| |
|
| | |
| | |
| |
|
| | function checkAttributeAccessibility(inst: any, attr: string) {
|
| | const instType = typeof inst;
|
| | check(
|
| | instType === "object" || instType === "function",
|
| | `Instance of type ${instType} does not have attributes.`,
|
| | );
|
| |
|
| |
|
| | check(!attr.startsWith("__") && !attr.endsWith("__"), `"${attr}" is not accessible.`);
|
| |
|
| | const attrType = typeof inst[attr];
|
| | if (attrType === "function") {
|
| | const allowedMethods = inst.constructor?.__ALLOWED_METHODS__ ?? inst.__ALLOWED_METHODS__ ?? [];
|
| | check(allowedMethods.includes(attr), `Method ${attr} is not accessible.`);
|
| | } else {
|
| | const allowedProps =
|
| | inst.constructor?.__ALLOWED_PROPERTIES__ ?? inst.__ALLOWED_PROPERTIES__ ?? [];
|
| | check(allowedProps.includes(attr), `Property ${attr} is not accessible.`);
|
| | }
|
| | }
|
| |
|
| | function maybeWrapValue(value: any) {
|
| | if (Array.isArray(value)) {
|
| | return new PyList(value);
|
| | }
|
| | return value;
|
| | }
|
| |
|
| | function isInt(value: any): value is number {
|
| | return typeof value === "number" && Math.round(value) === value;
|
| | }
|
| |
|
| | function isIntLike(value: any): boolean {
|
| | let is = isInt(value);
|
| | if (!is) {
|
| | is = typeof value === "string" && !!/^\d+$/.exec(value);
|
| | }
|
| | return is;
|
| | }
|
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | export function Exposed(target: any, key: string) {
|
| | const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
| | if (typeof descriptor?.value === "function") {
|
| | target.constructor.__ALLOWED_METHODS__ = target.constructor.__ALLOWED_METHODS__ || [];
|
| | target.constructor.__ALLOWED_METHODS__.push(key);
|
| | } else {
|
| | target.constructor.__ALLOWED_PROPERTIES__ = target.constructor.__ALLOWED_PROPERTIES__ || [];
|
| | target.constructor.__ALLOWED_PROPERTIES__.push(key);
|
| | }
|
| | }
|
| |
|