bds.js
Version:
A simple interpreter written to simulate and run BDScript Language in JavaScript
176 lines (175 loc) • 7.15 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Runtime = void 0;
const path_1 = __importDefault(require("path"));
const Context_1 = require("./Context");
const Environment_1 = require("./Environment");
const Evaluator_1 = require("./Evaluator");
const Lexer_1 = require("./Lexer");
const Parser_1 = require("./Parser");
const RuntimeBag_1 = require("./RuntimeBag");
class Runtime {
constructor() {
this.global = new Environment_1.Environment();
this.contexts = new Map();
this.evaluator = Evaluator_1.Evaluator.singleton;
this.options = {
alwaysStrict: false,
trimOutput: true
};
this._prepareGlobal();
}
runInput(fileName, input) {
const ast = new Parser_1.Parser().parseToAst(
/* Tokens */ new Lexer_1.Lexer(input).main(), this);
const ctx = this.prepareContext(fileName);
return this.evaluator.evaluate(ast, ctx);
}
prepareContext(fileName) {
let env = new Environment_1.Environment(this.global);
let bag = new RuntimeBag_1.RuntimeBag();
let ctx = new Context_1.Context(fileName, bag, env, this);
this.contexts.set(fileName, ctx);
return ctx;
}
getContext(fileName) {
return this.contexts.get(fileName);
}
_prepareGlobal() {
this.global.set("$print", async (ctx) => {
console.log(...(await ctx.evaluateArgs(ctx.getArgs())));
return "";
});
this.global.set("$async", (ctx) => {
new Promise((res, rej) => {
ctx.evaluateArgs(ctx.getArgs()).then(res).catch(rej);
});
return "";
});
this.global.set("$wait", async (ctx) => {
const [time = 1] = await ctx.evaluateArgs(ctx.getArgs(0, 1));
return new Promise((res) => setTimeout(() => res(""), time * 1000));
});
this.global.set("$filename", (ctx) => ctx.fileName);
this.global.set("$dirname", (ctx) => path_1.default.join(ctx.fileName, ".."));
this.global.set("$random", (ctx) => Math.random());
this.global.set("$if", async (ctx) => {
ctx.argsCheck(2); // Requires 2 arguments
const [condition, ifTrue, ifFalse] = ctx.getArgs(0, 3);
const op_idx = condition.child.findIndex((v) => v.type === "operator");
const op = condition.child[op_idx];
// if (!op) return Boolean(await ctx.evaluateArgs([condition])) ? ctx.evaluateArgs([ifTrue]) : ctx.evaluateArgs([ifFalse]);
const [cond_a, cond_b] = await ctx.evaluateArgs([
{ type: "argument", child: condition.child.slice(0, op_idx) },
{ type: "argument", child: condition.child.slice(op_idx + 1) },
]);
let res;
switch (op.value) {
case "==":
res = cond_a === cond_b;
break;
case "!=":
res = cond_a !== cond_b;
break;
case ">=":
res = cond_a >= cond_b;
break;
case ">":
res = cond_a > cond_b;
break;
case "<=":
res = cond_a <= cond_b;
break;
case "<":
res = cond_a < cond_b;
break;
}
if (res === true)
return ctx.evaluateArgs([ifTrue]);
if (res === false)
return ctx.evaluateArgs([ifFalse]);
throw new Error("Invalid operator!");
});
this.global.set('$safejs', async (ctx) => {
const evalCode = await ctx.evaluateArgs(ctx.getArgs(0));
const mask = {
ctx,
runtime: this,
evaluator: this.evaluator,
global
};
const res = (new Function("with(this) {'use strict';\n" + evalCode + "}").call(mask));
return res;
});
this.global.set("$def", async (ctx) => {
ctx.argsCheck(3);
const [fnarg, mapReturn, body] = ctx.getArgs();
let fnName;
if (fnarg.child[0]?.type === "call" ||
fnarg.child[0]?.type === "string") {
fnName =
(fnarg.child[0].type === "string" ? "$" : "") + fnarg.child[0].value;
}
else
throw new Error("FnName must be typeof string or call!");
ctx.env.set(fnName, (ctx) => {
return this.evaluator.visitArgument(body, ctx, mapReturn.child[0]?.type === "string" &&
["yes", "true"].includes(mapReturn.child[0]?.value?.toLowerCase?.()));
});
return '';
});
this.global.set("$max", async (ctx) => {
const nums = await ctx.evaluateArgs(ctx.getArgs());
return Math.max(...nums);
});
this.global.set("$min", async (ctx) => {
const nums = await ctx.evaluateArgs(ctx.getArgs());
return Math.min(...nums);
});
this.global.set("$round", async (ctx) => {
return Math.round(await ctx.evaluateArgs(ctx.getArgs(0, 1))[0]);
});
this.global.set("$floor", async (ctx) => {
return Math.floor(await ctx.evaluateArgs(ctx.getArgs(0, 1))[0]);
});
this.global.set("$trunc", async (ctx) => {
return Math.trunc(await ctx.evaluateArgs(ctx.getArgs(0, 1))[0]);
});
this.global.set("$sum", async (ctx) => {
const nums = await ctx.evaluateArgs(ctx.getArgs());
return nums.reduce((prev, v) => {
prev += v;
return prev;
}, 0);
});
this.global.set("$sub", async (ctx) => {
const nums = await ctx.evaluateArgs(ctx.getArgs());
return nums.reduce((prev, v) => {
prev -= v;
return prev;
}, 0);
});
this.global.set("$multi", async (ctx) => {
const nums = await ctx.evaluateArgs(ctx.getArgs());
return nums.reduce((prev, v) => {
prev *= v;
return prev;
}, 0);
});
this.global.set("$div", async (ctx) => {
const nums = await ctx.evaluateArgs(ctx.getArgs());
return nums.reduce((prev, v) => {
prev /= v;
return prev;
}, 0);
});
this.global.set("$modulo", async (ctx) => {
const nums = await ctx.evaluateArgs(ctx.getArgs(0, 2));
return nums.shift() % nums.shift();
});
}
}
exports.Runtime = Runtime;