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.
301 lines • 10.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Compiler = exports.Conditions = exports.Operators = exports.OperatorType = void 0;
const CompiledFunction_1 = require("../structures/CompiledFunction");
const ForgeError_1 = require("../structures/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: "\\",
Negation: "!",
Separator: ";"
};
static SystemRegex = /(\\+)?\[SYSTEM_FUNCTION\(\d+\)\]/gm;
static Regex;
static InvalidCharRegex = /(\$\{|`)/g;
static Functions = new discord_js_1.Collection();
id = 0;
matches;
index = 0;
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],
...(Compiler.Functions.get(`$${x[2]}`) ?? Compiler.Functions.find(fn => fn.name.toLowerCase() === `$${x[2].toLowerCase()}`)),
}));
}
else
this.matches = [];
}
compile() {
if (!this.code || this.matches.length === 0)
return {
code: this.code ?? "",
resolve: this.wrap(this.code ?? ""),
functions: [],
};
let out = "";
const functions = new Array();
while (this.matches.length !== 0) {
const match = this.matches.shift();
let escaped = false;
while (match.index !== this.index) {
const char = this.next();
const isEscape = char === Compiler.Syntax.Escape;
if (!escaped && isEscape) {
escaped = true;
continue;
}
escaped = false;
out += char;
}
if (escaped)
continue;
const fn = this.parseFunction(match);
out += fn.id;
functions.push(fn);
}
if (this.index !== this.code.length)
out += this.code.slice(this.index);
return {
code: out,
functions,
resolve: this.wrap(out),
};
}
parseFunction(match) {
this.moveTo(match.index + match.name.length + match.negated);
const char = this.char();
const usesFields = char === Compiler.Syntax.Open;
const name = match.name;
const id = this.getNextId();
if (match.args === null || (!usesFields && !match.args.required)) {
// Increment index if escape character, just to skip it.
if (char === Compiler.Syntax.Escape)
this.index++;
return {
id,
name,
negated: match.negated,
fields: null,
};
}
if (match.args.required && !usesFields) {
this.error(`Function ${match.name} requires brackets`);
}
const fields = new Array();
// Skip brace open
this.index++;
for (let i = 0, len = match.args.fields.length; i < len; i++) {
const isLast = i + 1 === match.args.fields.length;
const arg = match.args.fields[i];
if (arg.rest === true) {
for (;;) {
fields.push(this.parseField(match, arg));
if (this.char() === Compiler.Syntax.Separator)
this.index++;
else if (this.char() === Compiler.Syntax.Close)
break;
}
}
else {
fields.push(this.parseField(match, arg, isLast));
if (this.char() === Compiler.Syntax.Separator) {
this.index++;
if (!isLast)
continue;
}
}
const old = this.char();
if (isLast) {
if (old !== Compiler.Syntax.Close) {
this.error(`Function ${match.name} expects ${match.args.fields.length} arguments at most`);
}
}
else if (old === Compiler.Syntax.Close)
break;
}
// Skip closure
this.index++;
return {
id,
name,
negated: match.negated,
fields,
};
}
parseField(match, arg, requireEndBrace = false) {
let nextMatch = this.matches[0];
const condition = {};
let value = "";
const functions = new Array();
let braceClosure = false;
let escaped = false;
for (;;) {
const char = this.char();
if (char === undefined)
this.error("Reached end of code and found no brace closure for " + match.name);
const isEscape = char === Compiler.Syntax.Escape;
const isClosure = char === Compiler.Syntax.Close;
const isSeparator = char === Compiler.Syntax.Separator;
// Mark as escaped
if (!escaped && isEscape)
escaped = true;
if (!escaped) {
if (isClosure || isSeparator) {
if (isClosure)
braceClosure = true;
break;
}
else if (arg.condition === true && condition.op === undefined) {
const possibleOp = [char + this.peek(), char].find((x) => exports.Operators.has(x));
if (possibleOp !== undefined) {
this.index += possibleOp.length;
condition.op = possibleOp;
condition.lhs = {
functions: Array.from(functions),
value,
resolve: this.wrap(value),
};
functions.length = 0;
value = "";
continue;
}
}
}
if (nextMatch?.index === this.index) {
// Remove the function that we are about to parse
this.matches.shift();
if (!escaped) {
const fn = this.parseFunction(nextMatch);
functions.push(fn);
value += fn.id;
}
// Next function to match
nextMatch = this.matches[0];
if (!escaped)
continue;
}
if (!isEscape)
escaped = false;
this.index++;
if (!escaped)
value += char;
}
const data = {
functions,
value,
resolve: this.wrap(value),
};
if (arg.condition === true) {
condition.op ??= OperatorType.None;
condition.resolve = this.wrapCondition(condition.op);
if (!condition.lhs)
condition.lhs = data;
else if (!condition.rhs)
condition.rhs = data;
return condition;
}
return data;
}
error(str) {
const { line, column } = this.locate(this.index);
throw new ForgeError_1.ForgeError(null, ForgeError_1.ErrorType.CompilerError, str, line, column, this.path);
}
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) {
let out = "";
if (this.index >= index)
return out;
while (this.index !== index)
out += this.next();
return out;
}
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, x));
this.Regex = new RegExp(`\\$(\\!)?(${Array.from(this.Functions.values())
.sort((x, y) => y.name.length - x.name.length)
.map((x) => x.name.slice(1))
.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