Merge pull request #366 from danielyxie/jsinterpreter-import

Added import functionality to Netscript 1.0 with new JS Interpreter
This commit is contained in:
danielyxie 2018-07-20 09:53:44 -05:00 committed by GitHub
commit cdfcfb7df7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 247 additions and 98 deletions

File diff suppressed because one or more lines are too long

112
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

81
package-lock.json generated

@ -2180,8 +2180,7 @@
"deep-is": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
"dev": true
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
},
"define-properties": {
"version": "1.1.2",
@ -2643,33 +2642,27 @@
"dev": true
},
"escodegen": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz",
"integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=",
"dev": true,
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz",
"integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==",
"requires": {
"esprima": "2.7.3",
"estraverse": "1.9.3",
"esprima": "3.1.3",
"estraverse": "4.2.0",
"esutils": "2.0.2",
"optionator": "0.8.2",
"source-map": "0.2.0"
"source-map": "0.6.1"
},
"dependencies": {
"estraverse": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz",
"integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=",
"dev": true
"esprima": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
"integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM="
},
"source-map": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz",
"integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=",
"dev": true,
"optional": true,
"requires": {
"amdefine": "1.0.1"
}
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"optional": true
}
}
},
@ -2901,8 +2894,7 @@
"esutils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
"dev": true
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
},
"etag": {
"version": "1.8.1",
@ -3285,8 +3277,7 @@
"fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
},
"fastparse": {
"version": "1.1.1",
@ -4719,6 +4710,25 @@
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
"dev": true
},
"escodegen": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz",
"integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=",
"dev": true,
"requires": {
"esprima": "2.7.3",
"estraverse": "1.9.3",
"esutils": "2.0.2",
"optionator": "0.8.2",
"source-map": "0.2.0"
}
},
"estraverse": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz",
"integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=",
"dev": true
},
"glob": {
"version": "5.0.15",
"resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
@ -4744,6 +4754,16 @@
"integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
"dev": true
},
"source-map": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz",
"integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=",
"dev": true,
"optional": true,
"requires": {
"amdefine": "1.0.1"
}
},
"supports-color": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
@ -5216,7 +5236,6 @@
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
"dev": true,
"requires": {
"prelude-ls": "1.1.2",
"type-check": "0.3.2"
@ -6716,7 +6735,6 @@
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
"integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
"dev": true,
"requires": {
"deep-is": "0.1.3",
"fast-levenshtein": "2.0.6",
@ -6729,8 +6747,7 @@
"wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
"dev": true
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
}
}
},
@ -7908,8 +7925,7 @@
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
"dev": true
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
},
"prepend-http": {
"version": "1.0.4",
@ -10500,7 +10516,6 @@
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
"dev": true,
"requires": {
"prelude-ls": "1.1.2"
}

@ -15,6 +15,7 @@
"brace": "^0.11.1",
"decimal.js": "7.2.3",
"enhanced-resolve": "^4.0.0",
"escodegen": "^1.11.0",
"escope": "^3.6.0",
"file-saver": "^1.3.8",
"interpret": "^1.0.0",

@ -487,7 +487,12 @@ let CONSTANTS = {
LatestUpdate:
"v0.40.0<br>" +
"* Netscript 1.0 (NS1) now uses a fully-fledged ES5 Javascript Interpreter. This means many new features are now available in NS, and this also fixes several bugs.<br>" +
"* Netscript 1.0 (NS1) now uses a fully-fledged ES5 Javascript Interpreter. This means many new features are now available in NS1, and this also fixes several bugs." +
" However this also means any ES6+ features are no longer supported in NS1 <br>" +
"* When a server is hacked with a very large number of threads and left with no money, the server's security level " +
"now only increases by however many threads were needed to drain the server. For example, if you hack a server with " +
"5000 threads but it only needed 2000 threads to deplete the server's money, then the server's security will only increase " +
"as if you had hacked it with 2000 threads (change by hydroflame)<br>" +
"* Added getCurrentAction() to Bladeburner API<br>" +
"* Completely re-designed the Hacknet Node API<br>" +
"* getSkillLevel() in Bladeburner API now returns an error if no argument is passed in (as opposed to an object with all skill levels). This may break scripts<br>" +
@ -495,7 +500,7 @@ let CONSTANTS = {
"* HP is now reset (restored) when Augmenting<br>" +
"* Source-File 6 now increases both the level and experience gain of all combat stats (it was only experience gain previously)<br>" +
"* Reverted a previous change for Source-File 12. It's benefits are now multiplicative rather than additive<br>" +
"* Starting Infiltration security level for almost every location decreased by ~10%<br>" +
"* Starting Infiltration security level for almost every location decreased by ~10%<br>" +
"* Bug Fix: Infiltration buttons can no longer be clicked through NetscriptJS<br>"
}

@ -14,13 +14,18 @@ import {NetscriptPort} from "./NetscriptPort";
import {AllServers} from "./Server";
import {Settings} from "./Settings";
import {parse} from "../utils/acorn";
//TODO Maybe escodegen might be better?
import {generate} from 'escodegen';
import {parse, Node} from "../utils/acorn";
import {dialogBoxCreate} from "../utils/DialogBox";
import {compareArrays} from "../utils/helpers/compareArrays";
import {arrayToString} from "../utils/helpers/arrayToString";
import {roundToTwo} from "../utils/helpers/roundToTwo";
import {isString} from "../utils/StringHelperFunctions";
const walk = require("acorn/dist/walk");
function WorkerScript(runningScriptObj) {
this.name = runningScriptObj.filename;
this.running = false;
@ -160,6 +165,17 @@ function startNetscript1Script(workerScript) {
var code = workerScript.code;
workerScript.running = true;
//Process imports
var ast;
try {
ast = processNetscript1Imports(code, workerScript);
} catch(e) {
dialogBoxCreate("Error processing Imports in " + workerScript.name + ":<br>" + e);
workerScript.env.stopFlag = true;
workerScript.running = false;
return;
}
var interpreterInitialization = function(int, scope) {
//Add the Netscript environment
var ns = NetscriptFunctions(workerScript);
@ -209,7 +225,7 @@ function startNetscript1Script(workerScript) {
var interpreter;
try {
interpreter = new Interpreter(code, interpreterInitialization);
interpreter = new Interpreter(ast, interpreterInitialization);
} catch(e) {
dialogBoxCreate("Syntax ERROR in " + workerScript.name + ":<br>" + e);
workerScript.env.stopFlag = true;
@ -240,8 +256,6 @@ function startNetscript1Script(workerScript) {
try {
runInterpreter();
} catch(e) {
console.log("Caught in original");
console.log(e);
if (isString(e)) {
workerScript.errorMessage = e;
return reject(workerScript);
@ -252,8 +266,122 @@ function startNetscript1Script(workerScript) {
}
}
});
}
/* Since the JS Interpreter used for Netscript 1.0 only supports ES5, the keyword
'import' throws an error. However, since we want to support import funtionality
we'll implement it ourselves by parsing the Nodes in the AST out.
@param code - The script's code
@returns - ES5-compliant AST with properly imported functions
*/
function processNetscript1Imports(code, workerScript) {
//allowReserved prevents 'import' from throwing error in ES5
var ast = parse(code, {ecmaVersion:6, allowReserved:true, sourceType:"module"});
var server = workerScript.getServer();
if (server == null) {
throw new Error("Failed to find underlying Server object for script");
}
function getScript(scriptName) {
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === scriptName) {
return server.scripts[i];
}
}
return null;
}
var generatedCode = ""; //Generated Javascript Code
//Walk over the tree and process ImportDeclaration nodes
walk.simple(ast, {
ImportDeclaration: (node) => {
let scriptName = node.source.value;
let script = getScript(scriptName);
if (script == null) {
throw new Error("'Import' failed due to invalid script: " + scriptName);
}
let scriptAst = parse(script.code, {ecmaVersion:5, allowReserved:true, sourceType:"module"});
if (node.specifiers.length === 1 && node.specifiers[0].type === "ImportNamespaceSpecifier") {
//import * as namespace from script
let namespace = node.specifiers[0].local.name;
let fnNames = []; //Names only
let fnDeclarations = []; //FunctionDeclaration Node objects
walk.simple(scriptAst, {
FunctionDeclaration: (node) => {
fnNames.push(node.id.name);
fnDeclarations.push(node);
}
});
//Now we have to generate the code that would create the namespace
generatedCode =
"var " + namespace + ";\n" +
"(function (namespace) {\n";
//Add the function declarations
fnDeclarations.forEach((fn) => {
generatedCode += generate(fn);
generatedCode += "\n";
});
//Add functions to namespace
fnNames.forEach((fnName) => {
generatedCode += ("namespace." + fnName + " = " + fnName);
generatedCode += "\n";
});
//Finish
generatedCode += (
"})(" + namespace + " || " + "(" + namespace + " = {}));"
)
} else {
//import {...} from script
//Get array of all fns to import
let fnsToImport = [];
node.specifiers.forEach((e) => {
fnsToImport.push(e.local.name);
});
//Walk through script and get FunctionDeclaration code for all specified fns
let fnDeclarations = [];
walk.simple(scriptAst, {
FunctionDeclaration: (node) => {
if (fnsToImport.includes(node.id.name)) {
fnDeclarations.push(node);
}
}
});
//Convert FunctionDeclarations into code
fnDeclarations.forEach((fn) => {
generatedCode += generate(fn);
generatedCode += "\n";
});
}
}
});
//Remove ImportDeclarations from AST. These ImportDeclarations must be in top-level
if (ast.type !== "Program" || ast.body == null) {
throw new Error("Code could not be properly parsed");
}
for (let i = ast.body.length-1; i >= 0; --i) {
if (ast.body[i].type === "ImportDeclaration") {
ast.body.splice(i, 1);
}
}
//Convert the AST back into code
code = generate(ast);
//Add the imported code and re-generate in ES5 (JS Interpreter for NS1 only supports ES5);
code = generatedCode + code;
return parse(code, {ecmaVersion:5});
}
//Loop through workerScripts and run every script that is not currently running

@ -192,7 +192,7 @@ $(document).keydown(function(event) {
if (event.keyCode === KEY.TAB) {
event.preventDefault();
//Autocomplete
if (terminalInput == null) {return;}
var input = terminalInput.value;
@ -1253,7 +1253,7 @@ let Terminal = {
}
}
} else {
post("Error: Invalid file. Only scripts (.script), text files (.txt), or .fconf can be edited with nano"); return;
post("Error: Invalid file. Only scripts (.script, .ns, .js), text files (.txt), or .fconf can be edited with nano"); return;
}
Engine.loadScriptEditorContent(filename);
break;