175 lines
4.7 KiB
JavaScript
175 lines
4.7 KiB
JavaScript
|
/*
|
||
|
* Copyright (C) 2020 Alex Yatskov <alex@foosoft.net>
|
||
|
* Author: Alex Yatskov <alex@foosoft.net>
|
||
|
*
|
||
|
* This program is free software: you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation, either version 3 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
const fs = require('fs');
|
||
|
const vm = require('vm');
|
||
|
const path = require('path');
|
||
|
const assert = require('assert');
|
||
|
|
||
|
|
||
|
function getContextEnvironmentRecords(context, names) {
|
||
|
// Enables export of values from the declarative environment record
|
||
|
if (!Array.isArray(names) || names.length === 0) {
|
||
|
return [];
|
||
|
}
|
||
|
|
||
|
let scriptSource = '(() => {\n "use strict";\n const results = [];';
|
||
|
for (const name of names) {
|
||
|
scriptSource += `\n try { results.push(${name}); } catch (e) { results.push(void 0); }`;
|
||
|
}
|
||
|
scriptSource += '\n return results;\n})();';
|
||
|
|
||
|
const script = new vm.Script(scriptSource, {filename: 'getContextEnvironmentRecords'});
|
||
|
|
||
|
const contextHasNames = Object.prototype.hasOwnProperty.call(context, 'names');
|
||
|
const contextNames = context.names;
|
||
|
context.names = names;
|
||
|
|
||
|
const results = script.runInContext(context, {});
|
||
|
|
||
|
if (contextHasNames) {
|
||
|
context.names = contextNames;
|
||
|
} else {
|
||
|
delete context.names;
|
||
|
}
|
||
|
|
||
|
return Array.from(results);
|
||
|
}
|
||
|
|
||
|
function isDeepStrictEqual(val1, val2) {
|
||
|
if (val1 === val2) { return true; }
|
||
|
|
||
|
if (Array.isArray(val1)) {
|
||
|
if (Array.isArray(val2)) {
|
||
|
return isArrayDeepStrictEqual(val1, val2);
|
||
|
}
|
||
|
} else if (typeof val1 === 'object' && val1 !== null) {
|
||
|
if (typeof val2 === 'object' && val2 !== null) {
|
||
|
return isObjectDeepStrictEqual(val1, val2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function isArrayDeepStrictEqual(val1, val2) {
|
||
|
const ii = val1.length;
|
||
|
if (ii !== val2.length) { return false; }
|
||
|
|
||
|
for (let i = 0; i < ii; ++i) {
|
||
|
if (!isDeepStrictEqual(val1[i], val2[i])) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function isObjectDeepStrictEqual(val1, val2) {
|
||
|
const keys1 = Object.keys(val1);
|
||
|
const keys2 = Object.keys(val2);
|
||
|
|
||
|
if (keys1.length !== keys2.length) { return false; }
|
||
|
|
||
|
const keySet = new Set(keys1);
|
||
|
for (const key of keys2) {
|
||
|
if (!keySet.delete(key)) { return false; }
|
||
|
}
|
||
|
|
||
|
for (const key of keys1) {
|
||
|
if (!isDeepStrictEqual(val1[key], val2[key])) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const tag1 = Object.prototype.toString.call(val1);
|
||
|
const tag2 = Object.prototype.toString.call(val2);
|
||
|
if (tag1 !== tag2) { return false; }
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
function deepStrictEqual(actual, expected) {
|
||
|
try {
|
||
|
// This will fail on prototype === comparison on cross context objects
|
||
|
assert.deepStrictEqual(actual, expected);
|
||
|
} catch (e) {
|
||
|
if (!isDeepStrictEqual(actual, expected)) {
|
||
|
throw e;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
class VM {
|
||
|
constructor(context={}) {
|
||
|
this._context = vm.createContext(context);
|
||
|
this._assert = {
|
||
|
deepStrictEqual
|
||
|
};
|
||
|
}
|
||
|
|
||
|
get context() {
|
||
|
return this._context;
|
||
|
}
|
||
|
|
||
|
get assert() {
|
||
|
return this._assert;
|
||
|
}
|
||
|
|
||
|
get(names) {
|
||
|
if (typeof names === 'string') {
|
||
|
return getContextEnvironmentRecords(this._context, [names])[0];
|
||
|
} else if (Array.isArray(names)) {
|
||
|
return getContextEnvironmentRecords(this._context, names);
|
||
|
} else {
|
||
|
throw new Error('Invalid argument');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
set(values) {
|
||
|
if (typeof values === 'object' && values !== null) {
|
||
|
Object.assign(this._context, values);
|
||
|
} else {
|
||
|
throw new Error('Invalid argument');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
execute(fileNames) {
|
||
|
const single = !Array.isArray(fileNames);
|
||
|
if (single) {
|
||
|
fileNames = [fileNames];
|
||
|
}
|
||
|
|
||
|
const results = [];
|
||
|
for (const fileName of fileNames) {
|
||
|
const absoluteFileName = path.resolve(__dirname, '..', 'ext', fileName);
|
||
|
const source = fs.readFileSync(absoluteFileName, {encoding: 'utf8'});
|
||
|
const script = new vm.Script(source, {filename: absoluteFileName});
|
||
|
results.push(script.runInContext(this._context, {}));
|
||
|
}
|
||
|
|
||
|
return single ? results[0] : results;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
module.exports = {
|
||
|
VM
|
||
|
};
|