UNPKG

@leawind/rop

Version:

Evaluate expression with operator overloading and Python-style array slicing using tagged template literal.

1,007 lines (992 loc) 32.3 kB
// src/utils/TokenWalker.ts class TokenWalker { tokens; position = 0; constructor(tokens) { this.tokens = tokens; } getSource() { return this.tokens; } isFinished() { return this.position >= this.tokens.length; } getCurrentPosition() { return this.position; } hasRemaining() { return this.position < this.tokens.length; } getRemaining() { return this.tokens.slice(this.position); } peek(offset = 0) { const index = this.position + offset; if (index >= 0 && index < this.tokens.length) { return this.tokens[index] ?? null; } return null; } next(count) { if (count === undefined) { if (this.position < this.tokens.length) { return this.tokens[this.position++] ?? null; } return null; } else { const result = []; for (let i = 0;i < count; i++) { if (this.position < this.tokens.length) { const token = this.tokens[this.position++]; if (token !== undefined) { result.push(token); } } else { break; } } return result.length > 0 ? result : null; } } consume(count = 1) { this.position = Math.min(this.position + count, this.tokens.length); } skip(count = 1) { this.position = Math.min(this.position + count, this.tokens.length); } } // src/compiler/Operators.ts var UNARY_OPERATOR_NAMES = ["!", "~", "-x", "+x"]; var BINARY_OPERATOR_NAMES = [ "+", "-", "*", "/", "%", "**", "&", "|", "^", "<<", ">>", ">>>", "&&", "||", "==", "===", "!=", "!==", "<", ">", "<=", ">=" ]; var OPERATOR_NAMES = ["[i]", "[:]", ...UNARY_OPERATOR_NAMES, ...BINARY_OPERATOR_NAMES]; var OPERATION_REGISTRY = ((obj) => { for (const [name, meta] of Object.entries(obj)) { meta.name = name; meta.symbol = Symbol(name); obj[meta.symbol] = meta; } return obj; })({ "[i]": { type: "other" }, "[:]": { type: "other" }, "!": { type: "unary", literal: "!", native: (self) => !self, precedence: 10 }, "~": { type: "unary", literal: "~", native: (self) => ~self, precedence: 10 }, "-x": { type: "unary", literal: "-", native: (self) => -self, precedence: 10 }, "+x": { type: "unary", literal: "+", native: (self) => +self, precedence: 10 }, "||": { type: "binary", literal: "||", precedence: 1, native: (self, other) => self || other }, "&&": { type: "binary", literal: "&&", precedence: 2, native: (self, other) => self && other }, "|": { type: "binary", literal: "|", precedence: 3, native: (self, other) => self | other }, "^": { type: "binary", literal: "^", precedence: 4, native: (self, other) => self ^ other }, "&": { type: "binary", literal: "&", precedence: 5, native: (self, other) => self & other }, "==": { type: "binary", literal: "==", precedence: 6, native: (self, other) => self == other }, "===": { type: "binary", literal: "===", precedence: 6, native: (self, other) => self === other }, "!=": { type: "binary", literal: "!=", precedence: 6, native: (self, other) => self != other }, "!==": { type: "binary", literal: "!==", precedence: 6, native: (self, other) => self !== other }, "<": { type: "binary", literal: "<", precedence: 7, native: (self, other) => self < other }, ">": { type: "binary", literal: ">", precedence: 7, native: (self, other) => self > other }, "<=": { type: "binary", literal: "<=", precedence: 7, native: (self, other) => self <= other }, ">=": { type: "binary", literal: ">=", precedence: 7, native: (self, other) => self >= other }, "<<": { type: "binary", literal: "<<", precedence: 8, native: (self, other) => self << other }, ">>": { type: "binary", literal: ">>", precedence: 8, native: (self, other) => self >> other }, ">>>": { type: "binary", literal: ">>>", precedence: 8, native: (self, other) => self >>> other }, "+": { type: "binary", literal: "+", precedence: 9, native: (self, other) => self + other }, "-": { type: "binary", literal: "-", precedence: 9, native: (self, other) => self - other }, "*": { type: "binary", literal: "*", precedence: 10, native: (self, other) => self * other }, "/": { type: "binary", literal: "/", precedence: 10, native: (self, other) => self / other }, "%": { type: "binary", literal: "%", precedence: 10, native: (self, other) => self % other }, "**": { type: "binary", literal: "**", precedence: 11, native: (self, other) => self ** other } }); var OPERATOR_LITERAL_TO_NAME_MAP = (() => { const map = new Map; for (const [name, meta] of Object.entries(OPERATION_REGISTRY)) { switch (meta.type) { case "unary": case "binary": { const literal = meta.literal; if (!map.has(meta.literal)) { map.set(literal, {}); } map.get(literal)[meta.type] = name; } } } return map; })(); class Operations { constructor() {} static isKnownOperation(op) { return op in OPERATION_REGISTRY; } static unaryFromLiteral(literal) { return OPERATOR_LITERAL_TO_NAME_MAP.get(literal)?.unary || null; } static binaryFromLiteral(literal) { return OPERATOR_LITERAL_TO_NAME_MAP.get(literal)?.binary || null; } static meta(op) { return OPERATION_REGISTRY[op] ?? null; } static symbol(op) { return OPERATION_REGISTRY[op]?.symbol ?? null; } } // src/compiler/ast-parser/AstParser.ts class AstParser extends TokenWalker { constructor(tokens) { super(tokens); } parse() { this.skipWhitespace(); if (this.isFinished()) { throw new Error("Empty expression"); } const result = this.parseExpression(); this.skipWhitespace(); if (!this.isFinished()) { const remaining = this.getRemaining(); const t = `[ ` + remaining.map((x) => "\t" + JSON.stringify(x)).join(`, `) + ` ]`; throw new Error(`Unexpected token at end of expression: ${t}`); } return result; } skipWhitespace() { while (this.peek()?.type === "Whitespace" /* Whitespace */) { this.skip(); } } parseExpression(precedence = 0) { let left = this.parseAtom(); loop_parse_exp: while (true) { this.skipWhitespace(); const token = this.peek(); if (token === null) { break loop_parse_exp; } branch_token_type: switch (token.type) { case "Operator" /* Operator */: { const operator = Operations.binaryFromLiteral(token.literal); if (operator === null) { throw new Error(`Unexpected token: ${token}, binary operator expected`); } const meta = Operations.meta(operator); if (meta.type !== "binary") { throw new Error(`Never!`); } if (meta.precedence < precedence) { break loop_parse_exp; } this.consume(); const isRightAssociative = operator === "**"; const right = this.parseExpression(isRightAssociative ? meta.precedence : meta.precedence + 1); left = { type: "Binary" /* Binary */, left, operation: operator, right }; break branch_token_type; } case "Punctuation" /* Punctuation */: { switch (token.literal) { case ".": { this.consume(); this.skipWhitespace(); const prop = this.peek(); if (prop !== null && prop.type === "Identifier" /* Identifier */) { left = { type: "AccessProperty" /* AccessProperty */, left, name: prop.literal }; this.consume(); break branch_token_type; } throw new Error("Expected identifier after dot"); } case "(": { this.consume(); const args = []; while (true) { this.skipWhitespace(); if (this.tryConsumePunctuation(")")) { break; } const arg = this.parseExpression(); args.push(arg); this.skipWhitespace(); this.tryConsumePunctuation(","); } left = { type: "Invoke" /* Invoke */, target: left, args }; break branch_token_type; } case "[": { this.consume(); this.skipWhitespace(); if (this.peekPunctuation("]")) { this.consume(); throw new Error("Empty subscript is not allowed"); } const slices = []; let isSlicing = false; let elementCount = 0; while (true) { this.skipWhitespace(); const slice = (() => { const willBeExpression = () => { const p = this.peek(); return !(p !== null && p.type === "Punctuation" /* Punctuation */ && (p.literal === "]" || p.literal === "," || p.literal === ":")); }; const tryParseExpressionInSlice = () => { return willBeExpression() ? this.parseExpression() : null; }; const slice2 = []; let colons = 0; let hasExpression = false; while (true) { if (this.peekPunctuation(",") || this.peekPunctuation("]")) { break; } if (this.peekPunctuation(":")) { this.consume(); colons++; isSlicing = true; } else { const s = tryParseExpressionInSlice(); if (s !== null) { slice2[colons] = s; hasExpression = true; } } } if (hasExpression || colons > 0) { elementCount++; return { start: slice2[0], end: slice2[1], step: slice2[2] }; } return null; })(); if (slice !== null) { slices.push(slice); } this.skipWhitespace(); if (this.tryConsumePunctuation(",")) { isSlicing = true; } else if (this.tryConsumePunctuation("]")) { break; } } if (elementCount === 0) { throw new Error("Empty indexing or slicing expression"); } if (isSlicing || slices.length > 1) { left = { type: "Slicing" /* Slicing */, target: left, slices }; } else if (slices.length === 1) { const slice = slices[0]; if (slice.start && slice.end === undefined && slice.step === undefined) { left = { type: "Indexing" /* Indexing */, target: left, index: slice.start }; } else { left = { type: "Slicing" /* Slicing */, target: left, slices }; } } break branch_token_type; } default: break loop_parse_exp; } } default: break loop_parse_exp; } } return left; } parseAtom() { this.skipWhitespace(); const token = this.next(); if (token === null) { throw new Error("Unexpected end of expression"); } switch (token.type) { case "Interpolation" /* Interpolation */: case "Constant" /* Constant */: return { type: "Value" /* Value */, token }; case "Identifier" /* Identifier */: return { type: "Identifier" /* Identifier */, name: token.literal }; case "Operator" /* Operator */: { const unaryOperatorName = Operations.unaryFromLiteral(token.literal); if (!unaryOperatorName) { throw new Error(`Unexpected operator: '${token.literal}'`); } const meta = Operations.meta(unaryOperatorName); if (meta.type !== "unary") { throw new Error(`Never!`); } return { type: "Unary" /* Unary */, operation: unaryOperatorName, operand: this.parseExpression(meta.precedence) }; } case "Punctuation" /* Punctuation */: if (token.literal === "(") { const expr = this.parseExpression(); this.skipWhitespace(); if (this.tryConsumePunctuation(")") === null) { throw new Error("Expected closing parenthesis"); } return expr; } throw new Error(`Unexpected punctuation: ${token.literal}`); case "Whitespace" /* Whitespace */: throw new Error(`Unexpected whitespace token: '${token.literal}'`); default: throw new Error(`Unknown token type: ${token}`); } } peekPunctuation(literal) { const p = this.peek(); return p !== null && p.type === "Punctuation" /* Punctuation */ && (literal === undefined || p.literal === literal) ? p : null; } tryConsumePunctuation(punctuation) { const p = this.peek(); if (p !== null && p.type === "Punctuation" /* Punctuation */ && p.literal === punctuation) { return this.next(); } else { return null; } } } // src/compiler/evaluater/Evaluater.ts class Evaluater { ast; rop; constructor(ast, rop = Rop.INST) { this.ast = ast; this.rop = rop; } evaluate() { return this.evaluateNode(this.ast); } evaluateNode(node) { switch (node.type) { case "Value" /* Value */: return this.evaluateValueNode(node); case "Identifier" /* Identifier */: return this.evaluateIdentifierNode(node); case "Unary" /* Unary */: return this.evaluateUnaryNode(node); case "Binary" /* Binary */: return this.evaluateBinaryNode(node); case "AccessProperty" /* AccessProperty */: return this.evaluateAccessPropertyNode(node); case "Indexing" /* Indexing */: return this.evaluateIndexingNode(node); case "Slicing" /* Slicing */: return this.evaluateSlicingNode(node); case "Invoke" /* Invoke */: return this.evaluateInvokeNode(node); default: throw new Error(`Unknown node type: ${node.type}`); } } evaluateValueNode(node) { const token = node.token; if (token.type === "Interpolation" /* Interpolation */) { return token.value; } else if (token.type === "Constant" /* Constant */) { return token.value; } throw new Error(`Unknown value token type: ${token.type}`); } evaluateIdentifierNode(node) { const bindings = this.rop.bindings; if (!bindings.has(node.name)) { throw new Error(`Unknown identifier: ${node.name}`); } return bindings.get(node.name); } evaluateUnaryNode(node) { const operandValue = this.evaluateNode(node.operand); const meta = Operations.meta(node.operation); if (meta.type !== "unary") { throw new Error(`Invalid node: ${node}`); } const overload = this.rop.getOverloadOnInstance(operandValue, meta.symbol); if (typeof overload === "function") { return overload.call(operandValue); } return meta.native(operandValue, undefined); } evaluateBinaryNode(node) { const leftValue = this.evaluateNode(node.left); const rightValue = this.evaluateNode(node.right); const meta = Operations.meta(node.operation); if (meta.type !== "binary") { throw new Error(`Invalid node: ${node}`); } const leftOverload = this.rop.getOverloadOnInstance(leftValue, meta.symbol); if (typeof leftOverload === "function") { return leftOverload.call(leftValue, rightValue); } const rightOverload = this.rop.getOverloadOnInstance(rightValue, meta.symbol); if (typeof rightOverload === "function") { return rightOverload.call(rightValue, leftValue); } return meta.native(leftValue, rightValue); } evaluateAccessPropertyNode(node) { const leftValue = this.evaluateNode(node.left); return leftValue[node.name]; } evaluateInvokeNode(node) { const target = this.evaluateNode(node.target); const args = node.args.map((arg) => this.evaluateNode(arg)); if (typeof target !== "function") { throw new Error(`Cannot invoke non-function: ${typeof target}`); } return target(...args); } evaluateIndexingNode(node) { const target = this.evaluateNode(node.target); const fn = this.rop.getOverloadOnInstance(target, Operations.symbol("[i]")); if (typeof fn === "function") { return fn.call(target, this.evaluateNode(node.index)); } else { return target[this.evaluateNode(node.index)]; } } evaluateSlicingNode(node) { const target = this.evaluateNode(node.target); const fn = this.rop.getOverloadOnInstance(target, Operations.symbol("[:]")); if (typeof fn === "function") { return fn.call(target, node.slices.map((ns) => this.calculateSlice(ns))); } else { if (node.slices.length !== 1) { throw new Error("Target does not support slicing"); } const slice = node.slices[0]; if (slice.end !== undefined || slice.step !== undefined) { throw new Error("Target does not support slicing with end or step"); } return target[this.calculateSlice(slice).start]; } } calculateSlice(ns) { return { start: ns.start ? this.evaluateNode(ns.start) : undefined, end: ns.end ? this.evaluateNode(ns.end) : undefined, step: ns.step ? this.evaluateNode(ns.step) : undefined }; } } // src/error.ts class RopNeverError extends Error { constructor(message) { super(`RopNeverError: ${message} This error is caused by a bug in Rop!`); } } class CodeContext { source; begin; end; lines; constructor(source, begin, end = begin + 1) { this.source = source; this.begin = begin; this.end = end; if (begin < 0) { throw new RopNeverError("begin < 0"); } if (end <= begin) { throw new RopNeverError("end <= begin"); } if (end > source.length) { throw new RopNeverError("end > source.length"); } this.lines = source.split(` `).reduce((lines, content, row) => { lines.push({ content, row, offset: lines.reduce((ofs, li) => ofs + li.content.length + 1, 0) }); return lines; }, []); } toIndex(row, col) { let index = 0; for (let i = 0;i < row - 1; i++) { index += this.lines[i].content.length + 1; } return index + col; } toRowCol(index) { index = Math.min(Math.max(index, 0), this.source.length - 1); let row = 0; let col = 0; for (let i = 0;i < index; i++) { if (this.source[i] === ` `) { row++; col = 0; } else { col++; } } return [row, col]; } render(message = "", previousLineCount = 2) { const [beginRow, beginCol] = this.toRowCol(this.begin); const [endRow, endCol] = this.toRowCol(this.end); const lineNumberWidth = endRow.toString().length; const renderedLines = this.lines.slice(beginRow - previousLineCount, endRow + 1); let result = "\x1B[0m"; for (const line of renderedLines) { result += `\x1B[1m`; const lineNumberStr = `${(1 + line.row).toString().padStart(lineNumberWidth, " ")} | `; result += lineNumberStr; result += `\x1B[0m`; result += line.content; result += ` `; if (beginRow <= line.row && line.row <= endRow) { result += " ".repeat(lineNumberStr.length); const left = line.row === beginRow ? beginCol : 0; const right = line.row === endRow ? endCol : line.content.length; result += line.content.substring(0, left).replace(/[^\t]/g, " "); result += "\x1B[31m\x1B[1m"; result += line.content.substring(left, right).replace(/[^\t]/g, "^"); result += "\x1B[0m"; result += ` `; } } if (message) { result += `\x1B[31m${message}\x1B[0m `; } return result; } } class RopSyntaxError extends Error { context; constructor(context, reason) { super(`\x1B[91mROP Syntax Error: ${context.render(reason)}`); this.context = context; } } class TokenizingError extends RopSyntaxError { constructor(context, reason) { super(context, reason); } } // src/utils/StringWalker.ts class StringWalker { source; position = 0; constructor(source) { this.source = source; } getSource() { return this.source; } isFinished() { return this.position >= this.source.length; } hasRemaining() { return this.position < this.source.length; } getRemaining() { return this.source.substring(this.position); } peek(by) { switch (typeof by) { case "number": { return this.source.substring(this.position, this.position + by); } case "string": { return this.peek(by.length) === by ? by : null; } case "object": { return by instanceof RegExp ? this.getRemaining().match(by) : null; } } } next(by) { const result = this.peek(by); if (typeof result === "string") { this.position += result.length; return result; } if (Array.isArray(result)) { this.position += result[0].length; return result; } return null; } consume(len) { this.position = Math.min(this.position + len, this.source.length); } } // src/compiler/tokenizer/TokenFactory.ts class TokenFactory { constructor() {} static whitespace(literal) { return { type: "Whitespace" /* Whitespace */, literal }; } static operator(literal) { return { type: "Operator" /* Operator */, literal }; } static interpolation(value) { return { type: "Interpolation" /* Interpolation */, literal: "${}", value }; } static constant(literal, value) { return { type: "Constant" /* Constant */, literal, value }; } static punctuation(literal) { return { type: "Punctuation" /* Punctuation */, literal }; } static identifier(literal) { return { type: "Identifier" /* Identifier */, literal }; } } // src/compiler/tokenizer/Tokenizer.ts class Tokenizer extends StringWalker { ignoreWhitespace; constructor(input, ignoreWhitespace = true) { super(input); this.ignoreWhitespace = ignoreWhitespace; } tokenize() { this.source = Tokenizer.parseUnicodeEscapes(this.source); const tokens = []; while (this.hasRemaining()) { { const m = this.next(/^(\s|\n)+/); if (m !== null) { if (!this.ignoreWhitespace) { tokens.push({ type: "Whitespace" /* Whitespace */, literal: m[0] }); } continue; } } { const ch2 = this.peek(1); switch (ch2) { case "(": case ")": case "[": case "]": case "{": case "}": case ",": case ":": case ".": tokens.push(TokenFactory.punctuation(ch2)); this.consume(1); continue; } } { const m = this.next(/^(<=|>=|===|!==|==|!=|\*\*|>>>|>>|<<|&&|\|\||[+\-*/%&|^<>!~])/); if (m) { tokens.push(TokenFactory.operator(m[0])); continue; } } { { const m = this.next(/^[0-9]*\.?[0-9]+([eE][+-]?[0-9]+)?[n]?/); if (m) { const literal = m[0]; let value = 0; if (literal.endsWith("n")) { value = BigInt(literal.slice(0, -1)); } else if (literal.includes(".") || literal.includes("e") || literal.includes("E")) { value = parseFloat(literal); } else { value = parseInt(literal, 10); } tokens.push(TokenFactory.constant(literal, value)); continue; } } { const m = this.next(/^'([^'\\]|\\.)*'/); if (m) { const literal = m[0]; const value = literal.slice(1, -1).replace(/\\'/g, "'").replace(/\\\\/g, "\\"); tokens.push(TokenFactory.constant(literal, value)); continue; } } { const m = this.next(/^"([^"\\]|\\.)*"/); if (m) { const literal = m[0]; const value = literal.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\"); tokens.push(TokenFactory.constant(literal, value)); continue; } } } { const m = this.next(/^([$_\p{ID_Start}][$_\p{ID_Continue}]*)/u); if (m) { const literal = m[0]; tokens.push(TokenFactory.identifier(literal)); continue; } } const ctx = new CodeContext(this.source, this.position); const ch = this.peek(1); const hex = ch.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0"); throw new TokenizingError(ctx, `Unexpected character '${ch}', code is \\u${hex}`); } return tokens; } static tokenize(s, ...args) { if (typeof s === "string") { return new Tokenizer(s).tokenize(); } else { let tokens = []; for (let i = 0;i < args.length; i++) { tokens.push(...new Tokenizer(s.raw[i]).tokenize()); tokens.push(TokenFactory.interpolation(args[i])); } tokens.push(...this.tokenize(s.raw.at(-1))); return tokens; } } static parseUnicodeEscapes(str) { return str.replace(/\\u([0-9A-Fa-f]{4,})/g, (_, hex) => { return String.fromCodePoint(parseInt(hex, 16)); }).replace(/\\u\{([0-9A-Fa-f]{4,})\}/g, (_, hex) => { return String.fromCodePoint(parseInt(hex, 16)); }); } } // src/utils/index.ts function detectFunctionType(fn) { if (fn.hasOwnProperty("prototype")) { return "normal"; } const str = Function.prototype.toString.call(fn); if (str.startsWith("(") || /^[^(),.=>{}[]]+\s*=>\s*\{/.test(str)) { return "arrow"; } return "method"; } function normalizeIndex(index, length) { return index < 0 ? index + length : index; } // src/Rop.ts class Rop { overloadings = new Map; bindings = new Map; constructor() {} o(strs, ...args) { const tokens = Tokenizer.tokenize(strs, ...args); const ast = new AstParser(tokens).parse(); const result = new Evaluater(ast, this).evaluate(); return result; } bind(...args) { if (typeof args[0] === "string") { const [key, value] = args; this.bindings.set(key, value); } else if (args[0] instanceof Map) { for (const [key, value] of args[0].entries()) { this.bindings.set(key, value); } } else { for (const [key, value] of Object.entries(args[0])) { this.bindings.set(key, value); } } return this; } unbind(...keys) { for (const k of keys) { this.bindings.delete(k); } return this; } static op(name) { if (!Operations.isKnownOperation(name)) { throw new Error(`Unknown operation name: '${name}'`); } return Operations.symbol(name); } setOverload(prototype, symbol, operationFn) { if (!this.overloadings.has(prototype)) { this.overloadings.set(prototype, new Map); } const classOverloads = this.overloadings.get(prototype); switch (detectFunctionType(operationFn)) { case "normal": case "method": classOverloads.set(symbol, operationFn); break; case "arrow": classOverloads.set(symbol, function(...args) { return operationFn(this, ...args); }); break; } } overload(clazz, op, operationFn) { if (clazz.prototype === undefined) { throw new TypeError("clazz must be a class"); } const symbol = Operations.symbol(op); if (symbol === null) { throw new TypeError(`Unknown operation: ${String(op)}`); } this.setOverload(clazz.prototype, symbol, operationFn); return this; } overloads(clazz, def) { if (clazz.prototype === undefined) { throw new TypeError("clazz must be a class"); } for (const key of Reflect.ownKeys(def)) { const symbol = Operations.symbol(key); if (symbol === null) { continue; } const operationFn = def[key]; if (typeof operationFn !== "function") { throw new TypeError(`Expected operation function '${symbol.description}' to be a function, but got ${typeof operationFn}`); } this.setOverload(clazz.prototype, symbol, operationFn); } return this; } getOverloadFromPrototypeChain(prototype, symbol) { let p = prototype; while (p !== null) { const classOverloads = this.overloadings.get(p); if (classOverloads !== undefined && classOverloads.has(symbol)) { return classOverloads.get(symbol) ?? null; } if (typeof p === "object" && symbol in p && typeof p[symbol] === "function") { return p[symbol]; } p = Object.getPrototypeOf(p); } return null; } getOverloadOnClass(clazz, symbol) { return this.getOverloadFromPrototypeChain(clazz.prototype, symbol); } getOverloadOnInstance(inst, symbol) { return this.getOverloadFromPrototypeChain(inst, symbol); } bindDefaults() { return this.bind({ true: true, false: false, null: null, undefined: undefined, Infinity: Infinity, NaN: NaN, Object, Number, BigInt, String, Boolean, Array, Date, Symbol, JSON, Math }); } bindMaths() { return this.bind(Object.getOwnPropertyNames(Math).reduce((m, k) => { Reflect.set(m, k, Reflect.get(Math, k)); return m; }, {})); } overloadDefaults() { this.overloads(Array, { "+": (self, other) => [...self, ...other], "[i]"(index) { if (typeof index !== "number") { throw new Error("Index of Array must be a number"); } return this[normalizeIndex(index, this.length)]; }, "[:]"(slices) { if (slices.length !== 1) { throw new Error("Multi slice is not supported"); } const slice = slices[0]; if (slice.step === 0) { throw new Error("Slice step cannot be zero"); } else { slice.step ??= 1; const result = []; if (slice.step > 0) { slice.start = slice.start === undefined ? 0 : normalizeIndex(slice.start, this.length); slice.end = slice.end === undefined ? this.length : normalizeIndex(slice.end, this.length); for (let i = slice.start;i < slice.end; i += slice.step) { result.push(this[i]); } } else { slice.start = slice.start === undefined ? this.length - 1 : normalizeIndex(slice.start, this.length); slice.end = slice.end === undefined ? -1 : normalizeIndex(slice.end, this.length); for (let i = slice.start;i > slice.end; i += slice.step) { result.push(this[i]); } } return result; } } }); this.overloads(String, { "*": (self, n) => self.repeat(n) }); this.overloads(Set, { "+": (self, b) => new Set([...self, ...b]), "-": (self, b) => new Set([...self].filter((x) => !b.has(x))) }); return this; } static instance; static get INST() { if (this.instance === undefined) { this.instance = new Rop().bindDefaults().bindMaths().overloadDefaults(); } return this.instance; } static resetDefaultInstance() { this.instance = undefined; } } // src/index.ts var o = Rop.INST.o; export { o, Rop };