aboutsummaryrefslogtreecommitdiff
path: root/src/client/util/Scripting.ts
diff options
context:
space:
mode:
authorBob Zeleznik <zzzman@gmail.com>2019-04-14 22:59:58 -0400
committerBob Zeleznik <zzzman@gmail.com>2019-04-14 22:59:58 -0400
commitc6360fb4aed348f6f6a3c7412b6acc0d1990c239 (patch)
tree3018ea887b40bacac92a137206d5e5f5c34a12bd /src/client/util/Scripting.ts
parent5fbee077873c3dd0a9b5939babbaa1fd4dfe1393 (diff)
parentc787b0eac374b4dabf6ede7ee40e77a28815d5c8 (diff)
merged with master
Diffstat (limited to 'src/client/util/Scripting.ts')
-rw-r--r--src/client/util/Scripting.ts127
1 files changed, 90 insertions, 37 deletions
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 4e97b9401..c67cc067a 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -14,45 +14,68 @@ import { ListField } from "../../fields/ListField";
// import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts'
// @ts-ignore
-import * as typescriptlib from '!!raw-loader!./type_decls.d'
+import * as typescriptlib from '!!raw-loader!./type_decls.d';
import { Documents } from "../documents/Documents";
import { Key } from "../../fields/Key";
+export interface ScriptSucccess {
+ success: true;
+ result: any;
+}
+
+export interface ScriptError {
+ success: false;
+ error: any;
+}
-export interface ExecutableScript {
- (): any;
+export type ScriptResult = ScriptSucccess | ScriptError;
+
+export interface CompiledScript {
+ readonly compiled: true;
+ readonly originalScript: string;
+ readonly options: Readonly<ScriptOptions>;
+ run(args?: { [name: string]: any }): ScriptResult;
+}
- compiled: boolean;
+export interface CompileError {
+ compiled: false;
+ errors: any[];
}
-function Compile(script: string | undefined, diagnostics: Opt<any[]>, scope: { [name: string]: any }): ExecutableScript {
- const compiled = !(diagnostics && diagnostics.some(diag => diag.category == ts.DiagnosticCategory.Error));
+export type CompileResult = CompiledScript | CompileError;
- let func: () => Opt<Field>;
- if (compiled && script) {
- let fieldTypes = [Document, NumberField, TextField, ImageField, RichTextField, ListField, Key];
- let paramNames = ["KeyStore", "Documents", ...fieldTypes.map(fn => fn.name)];
- let params: any[] = [KeyStore, Documents, ...fieldTypes]
- for (let prop in scope) {
- if (prop === "this") {
+function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult {
+ const errors = diagnostics.some(diag => diag.category === ts.DiagnosticCategory.Error);
+ if (errors || !script) {
+ return { compiled: false, errors: diagnostics };
+ }
+
+ let fieldTypes = [Document, NumberField, TextField, ImageField, RichTextField, ListField, Key];
+ let paramNames = ["KeyStore", "Documents", ...fieldTypes.map(fn => fn.name)];
+ let params: any[] = [KeyStore, Documents, ...fieldTypes];
+ let compiledFunction = new Function(...paramNames, `return ${script}`);
+ let { capturedVariables = {} } = options;
+ let run = (args: { [name: string]: any } = {}): ScriptResult => {
+ let argsArray: any[] = [];
+ for (let name of customParams) {
+ if (name === "this") {
continue;
}
- paramNames.push(prop);
- params.push(scope[prop]);
+ if (name in args) {
+ argsArray.push(args[name]);
+ } else {
+ argsArray.push(capturedVariables[name]);
+ }
}
- let thisParam = scope["this"];
- let compiledFunction = new Function(...paramNames, script);
- func = function (): Opt<Field> {
- return compiledFunction.apply(thisParam, params)
- };
- } else {
- func = () => undefined;
- }
-
- return Object.assign(func,
- {
- compiled
- });
+ let thisParam = args.this || capturedVariables.this;
+ try {
+ const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray);
+ return { success: true, result };
+ } catch (error) {
+ return { success: false, error };
+ }
+ };
+ return { compiled: true, run, originalScript, options };
}
interface File {
@@ -74,14 +97,14 @@ class ScriptingCompilerHost {
}
// getDefaultLibFileName(options: ts.CompilerOptions): string {
getDefaultLibFileName(options: any): string {
- return 'node_modules/typescript/lib/lib.d.ts' // No idea what this means...
+ return 'node_modules/typescript/lib/lib.d.ts'; // No idea what this means...
}
writeFile(fileName: string, content: string) {
const file = this.files.find(file => file.fileName === fileName);
if (file) {
file.content = content;
} else {
- this.files.push({ fileName, content })
+ this.files.push({ fileName, content });
}
}
getCurrentDirectory(): string {
@@ -108,26 +131,56 @@ class ScriptingCompilerHost {
}
}
-export function CompileScript(script: string, scope?: { [name: string]: any }, addReturn: boolean = false): ExecutableScript {
+export interface ScriptOptions {
+ requiredType?: string;
+ addReturn?: boolean;
+ params?: { [name: string]: string };
+ capturedVariables?: { [name: string]: Field };
+}
+
+export function CompileScript(script: string, options: ScriptOptions = {}): CompileResult {
+ const { requiredType = "", addReturn = false, params = {}, capturedVariables = {} } = options;
let host = new ScriptingCompilerHost;
- let funcScript = `(function() {
+ let paramNames: string[] = [];
+ if ("this" in params || "this" in capturedVariables) {
+ paramNames.push("this");
+ }
+ for (const key in params) {
+ if (key === "this") continue;
+ paramNames.push(key);
+ }
+ let paramList = paramNames.map(key => {
+ const val = params[key];
+ return `${key}: ${val}`;
+ });
+ for (const key in capturedVariables) {
+ if (key === "this") continue;
+ paramNames.push(key);
+ paramList.push(`${key}: ${capturedVariables[key].constructor.name}`);
+ }
+ let paramString = paramList.join(", ");
+ let funcScript = `(function(${paramString})${requiredType ? `: ${requiredType}` : ''} {
${addReturn ? `return ${script};` : script}
- }).apply(this)`
+ })`;
host.writeFile("file.ts", funcScript);
host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib);
let program = ts.createProgram(["file.ts"], {}, host);
let testResult = program.emit();
- let outputText = "return " + host.readFile("file.js");
+ let outputText = host.readFile("file.js");
let diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics);
- return Compile(outputText, diagnostics, scope || {});
+ return Run(outputText, paramNames, diagnostics, script, options);
+}
+
+export function OrLiteralType(returnType: string): string {
+ return `${returnType} | string | number`;
}
export function ToField(data: any): Opt<Field> {
- if (typeof data == "string") {
+ if (typeof data === "string") {
return new TextField(data);
- } else if (typeof data == "number") {
+ } else if (typeof data === "number") {
return new NumberField(data);
}
return undefined;