UNPKG

@tryforge/forgescript

Version:

ForgeScript is a comprehensive package that empowers you to effortlessly interact with Discord's API. It ensures scripting remains easy to learn and consistently effective.

363 lines 12.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Compiler = exports.Conditions = exports.Operators = exports.OperatorType = void 0; const CompiledFunction_1 = require("../structures/@internal/CompiledFunction"); const ForgeError_1 = require("../structures/forge/ForgeError"); const discord_js_1 = require("discord.js"); var OperatorType; (function (OperatorType) { OperatorType["Eq"] = "=="; OperatorType["NotEq"] = "!="; OperatorType["Lte"] = "<="; OperatorType["Gte"] = ">="; OperatorType["Gt"] = ">"; OperatorType["Lt"] = "<"; OperatorType["None"] = "unknown"; })(OperatorType || (exports.OperatorType = OperatorType = {})); exports.Operators = new Set(Object.values(OperatorType)); exports.Conditions = { unknown: (lhs, rhs) => lhs === "true", "!=": (lhs, rhs) => lhs !== rhs, "==": (lhs, rhs) => lhs === rhs, "<": (lhs, rhs) => Number(lhs) < Number(rhs), "<=": (lhs, rhs) => Number(lhs) <= Number(rhs), ">": (lhs, rhs) => Number(lhs) > Number(rhs), ">=": (lhs, rhs) => Number(lhs) >= Number(rhs), }; /** * REWRITE NEEDED */ class Compiler { path; code; static Syntax = { Open: "[", Close: "]", Escape: "\\", Count: "@", Negation: "!", Separator: ";", Silent: "#" }; static SystemRegex = /(\\+)?\[SYSTEM_FUNCTION\(\d+\)\]/gm; static Regex; static InvalidCharRegex = /(\\|\${|`)/g; static Functions = new discord_js_1.Collection(); static EscapeRegex = /(\.|\$|\(|\)|\*|\[|\]|\{|\}|\?|!|\^)/gim; id = 0; matches; matchIndex = 0; index = 0; outputFunctions = new Array(); outputCode = ""; constructor(path, code) { this.path = path; this.code = code; if (code) { this.matches = Array.from(code.matchAll(Compiler.Regex)).map((x) => ({ index: x.index, negated: !!x[1], silent: !!x[2], length: x[0].length, count: x[4] ?? null, fn: this.getFunction(x[5]), })); } else this.matches = []; } compile() { if (this.matches.length !== 0) { // Loop while functions are unmatched let match; loop: while ((match = this.match) !== undefined) { while (match.index !== this.index) { const char = this.char(); const { isEscape } = this.getCharInfo(char); if (isEscape) { const { char } = this.processEscape(); this.outputCode += char; continue loop; } this.outputCode += char; this.index++; } const parsed = this.parseFunction(); this.outputFunctions.push(parsed); this.outputCode += parsed.id; } this.outputCode += this.code.slice(this.index); } else this.outputCode = this.code ?? ""; return { code: this.outputCode, functions: this.outputFunctions, resolve: this.wrap(this.outputCode), }; } parseFunction() { // Skip this match already const match = this.matches[this.matchIndex++]; // Skip function this.skip(match.length); const hasFields = this.code[this.index] === Compiler.Syntax.Open; if (!hasFields && match.fn.args?.required) { this.error(`Function ${match.fn.name} requires brackets`); } else if (!hasFields || match.fn.args === null) { return this.prepareFunction(match, null); } // Skip [ this.skip(1); const fields = new Array(); // Field parsing for (let i = 0, len = match.fn.args.fields.length; i < len; i++) { let isLast = i + 1 === len; const field = match.fn.args.fields[i]; if (!field.rest) { fields.push(this.parseAnyField(match, field)); } else { for (;;) { fields.push(this.parseAnyField(match, field)); if (this.back() !== Compiler.Syntax.Separator) break; } } const isSeparator = this.back() === Compiler.Syntax.Separator; if (!isSeparator) break; else if (isLast && isSeparator) { this.error(`Function ${match.fn.name} expects ${match.fn.args?.fields.length} arguments at most`); } } return this.prepareFunction(match, fields); } getCharInfo(char) { return { isSeparator: char === Compiler.Syntax.Separator, isClosure: char === Compiler.Syntax.Close, isEscape: char === Compiler.Syntax.Escape, }; } parseFieldMatch(fns, match) { const fn = this.parseFunction(); fns.push(fn); // Next match return { nextMatch: this.match, fn, }; } processEscape() { this.index++; const next = this.char(); const now = this.match; if (now && now.index === this.index) this.matchIndex++; this.index++; return { nextMatch: this.match, char: next, }; } parseConditionField(ref) { const data = {}; const functions = new Array(); let fieldValue = ""; let closedGracefully = false; let match = this.match; let char; while ((char = this.char()) !== undefined) { const { isClosure, isEscape, isSeparator } = this.getCharInfo(char); if (isEscape) { const { char, nextMatch } = this.processEscape(); fieldValue += char; match = nextMatch; continue; } if (isClosure || isSeparator) { closedGracefully = true; break; } if (match?.index === this.index) { const { fn, nextMatch } = this.parseFieldMatch(functions, match); match = nextMatch; fieldValue += fn.id; continue; } if (data.op === undefined) { const possibleOperator = [char + this.peek(), char].find((x) => exports.Operators.has(x)); if (possibleOperator) { data.op = possibleOperator; data.lhs = { functions: [...functions], resolve: this.wrap(fieldValue), value: fieldValue, }; fieldValue = ""; functions.length = 0; this.index += data.op.length; continue; } } fieldValue += char; this.index++; } if (!closedGracefully) this.error(`Function ${ref.fn.name} is missing brace closure`); const out = { functions, value: fieldValue, resolve: this.wrap(fieldValue), }; if (data.op) data.rhs = out; else data.lhs = out; data.op ??= OperatorType.None; data.resolve = this.wrapCondition(data.op); return data; } parseNormalField(ref) { const functions = new Array(); let fieldValue = ""; let closedGracefully = false; let match = this.match; let char; while ((char = this.char()) !== undefined) { const { isClosure, isEscape, isSeparator } = this.getCharInfo(char); if (isEscape) { const { char, nextMatch } = this.processEscape(); fieldValue += char; match = nextMatch; continue; } if (isClosure || isSeparator) { closedGracefully = true; break; } if (match?.index === this.index) { const { fn, nextMatch } = this.parseFieldMatch(functions, match); match = nextMatch; fieldValue += fn.id; continue; } fieldValue += char; this.index++; } if (!closedGracefully) this.error(`Function ${ref.fn.name} is missing brace closure`); return { resolve: this.wrap(fieldValue), functions, value: fieldValue, }; } parseAnyField(ref, field) { const fld = field.condition ? this.parseConditionField(ref) : this.parseNormalField(ref); this.skip(1); return fld; } prepareFunction(match, fields) { const id = this.getNextId(); return { index: this.id - 1, id, fields, count: match.count, silent: match.silent, name: match.fn.name, negated: match.negated, }; } skip(n) { return this.moveTo(n + this.index); } skipIf(char) { if (char === this.code[this.index]) return this.skip(1), true; return false; } get match() { return this.matches[this.matchIndex]; } getFunction(str) { const fn = `$${str.toLowerCase()}`; return (Compiler.Functions.get(fn) ?? Compiler.Functions.find((x) => x.aliases?.some((x) => x.toLowerCase() === fn)) ?? this.error(`Function ${fn} is not registered.`)); } error(str) { const { line, column } = this.locate(this.index); throw new ForgeError_1.ForgeError(null, ForgeError_1.ErrorType.CompilerError, str, line, column, this.path ?? "index file"); } locate(index) { const data = { column: 0, line: 1, }; for (let i = 0; i < index; i++) { const char = this.code[i]; if (char === "\n") data.line++, (data.column = 0); else data.column++; } return data; } back() { return this.code[this.index - 1]; } wrapCondition(op) { return exports.Conditions[op]; } wrap(code) { let i = 0; const gencode = code.replace(Compiler.InvalidCharRegex, "\\$1").replace(Compiler.SystemRegex, () => { return "${args[" + i++ + "] ?? ''}"; }); return new Function("args", "return `" + gencode + "`"); } moveTo(index) { this.index = index; } getNextId() { return `[SYSTEM_FUNCTION(${this.id++})]`; } char() { return this.code[this.index]; } peek() { return this.code[this.index + 1]; } next() { return this.code[this.index++]; } static setFunctions(fns) { fns.map((x) => { this.Functions.set(x.name.toLowerCase(), x); x.aliases ?.filter((x) => typeof x === "string") ?.map((alias) => this.Functions.set(alias.toLowerCase(), x)); }); const mapped = Array.from(this.Functions.keys()); this.Regex = new RegExp(`\\$(\\!)?(\\#)?(@\\[(.*?)\\])?(${mapped .map((x) => (x.startsWith("$") ? x.slice(1).toLowerCase() : x.toLowerCase()).replace(Compiler.EscapeRegex, "\\$1")) .sort((x, y) => y.length - x.length) .join("|")})`, "gim"); } static compile(code, path) { const result = new this(path, code).compile(); return { ...result, functions: result.functions.map((x) => new CompiledFunction_1.CompiledFunction(x)), }; } static setSyntax(syntax) { Reflect.set(Compiler, "Syntax", syntax); } } exports.Compiler = Compiler; //# sourceMappingURL=Compiler.js.map