summaryrefslogtreecommitdiff
path: root/src/client/app/common/scripts
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2019-05-01 14:54:34 +0900
committersyuilo <syuilotan@yahoo.co.jp>2019-05-01 14:54:34 +0900
commit52ebf2055e0d6a238796ceacf92a7073bf007127 (patch)
tree1dbfe42b002e5611346a6073695ea113b6872942 /src/client/app/common/scripts
parentImprove MisskeyPages (diff)
downloadsharkey-52ebf2055e0d6a238796ceacf92a7073bf007127.tar.gz
sharkey-52ebf2055e0d6a238796ceacf92a7073bf007127.tar.bz2
sharkey-52ebf2055e0d6a238796ceacf92a7073bf007127.zip
Improve AiScript
Diffstat (limited to 'src/client/app/common/scripts')
-rw-r--r--src/client/app/common/scripts/aiscript.ts134
1 files changed, 70 insertions, 64 deletions
diff --git a/src/client/app/common/scripts/aiscript.ts b/src/client/app/common/scripts/aiscript.ts
index 70edce9b7b..1c4fe217c3 100644
--- a/src/client/app/common/scripts/aiscript.ts
+++ b/src/client/app/common/scripts/aiscript.ts
@@ -101,7 +101,6 @@ const literalDefs = {
textList: { out: 'stringArray', category: 'value', icon: faList, },
number: { out: 'number', category: 'value', icon: faSortNumericUp, },
ref: { out: null, category: 'value', icon: faSuperscript, },
- in: { out: null, category: 'value', icon: faSuperscript, },
fn: { out: 'function', category: 'value', icon: faSuperscript, },
};
@@ -139,6 +138,50 @@ const envVarsDef = {
YMD: 'string',
};
+class AiScriptError extends Error {
+ constructor(...params) {
+ super(...params);
+
+ // Maintains proper stack trace for where our error was thrown (only available on V8)
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, AiScriptError);
+ }
+ }
+}
+
+class Scope {
+ private layerdStates: Record<string, any>[];
+ public name: string;
+
+ constructor(layerdStates: Scope['layerdStates'], name?: Scope['name']) {
+ this.layerdStates = layerdStates;
+ this.name = name || 'anonymous';
+ }
+
+ @autobind
+ public createChildScope(states: Record<string, any>, name?: Scope['name']): Scope {
+ const layer = [states, ...this.layerdStates];
+ return new Scope(layer, name);
+ }
+
+ /**
+ * 指定した名前の変数の値を取得します
+ * @param name 変数名
+ */
+ @autobind
+ public getState(name: string): any {
+ for (const later of this.layerdStates) {
+ const state = later[name];
+ if (state !== undefined) {
+ return state;
+ }
+ }
+
+ throw new AiScriptError(
+ `No such variable '${name}' in scope '${this.name}'`);
+ }
+}
+
export class AiScript {
private variables: Variable[];
private pageVars: PageVar[];
@@ -298,7 +341,7 @@ export class AiScript {
return null;
}
if (v.type === 'fn') return null; // todo
- if (v.type === 'in') return null; // todo
+ if (v.type.startsWith('fn:')) return null; // todo
const generic: Type[] = [];
@@ -350,43 +393,34 @@ export class AiScript {
}
@autobind
- private interpolate(str: string, values: { name: string, value: any }[]) {
+ private interpolate(str: string, scope: Scope) {
return str.replace(/\{(.+?)\}/g, match => {
- const v = this.getVarVal(match.slice(1, -1).trim(), values);
+ const v = scope.getState(match.slice(1, -1).trim());
return v == null ? 'NULL' : v.toString();
});
}
@autobind
- public evaluateVars() {
- const values: { name: string, value: any }[] = [];
+ public evaluateVars(): Record<string, any> {
+ const values: Record<string, any> = {};
- for (const v of this.variables) {
- values.push({
- name: v.name,
- value: this.evaluate(v, values)
- });
+ for (const [k, v] of Object.entries(this.envVars)) {
+ values[k] = v;
}
for (const v of this.pageVars) {
- values.push({
- name: v.name,
- value: v.value
- });
+ values[v.name] = v.value;
}
- for (const [k, v] of Object.entries(this.envVars)) {
- values.push({
- name: k,
- value: v
- });
+ for (const v of this.variables) {
+ values[v.name] = this.evaluate(v, new Scope([values]));
}
return values;
}
@autobind
- private evaluate(block: Block, values: { name: string, value: any }[], slotArg: Record<string, any> = {}): any {
+ private evaluate(block: Block, scope: Scope): any {
if (block.type === null) {
return null;
}
@@ -396,7 +430,7 @@ export class AiScript {
}
if (block.type === 'text' || block.type === 'multiLineText') {
- return this.interpolate(block.value || '', values);
+ return this.interpolate(block.value || '', scope);
}
if (block.type === 'textList') {
@@ -404,28 +438,27 @@ export class AiScript {
}
if (block.type === 'ref') {
- return this.getVarVal(block.value, values);
- }
-
- if (block.type === 'in') {
- return slotArg[block.value];
+ return scope.getState(block.value);
}
if (isFnBlock(block)) { // ユーザー関数定義
return {
slots: block.value.slots.map(x => x.name),
- exec: slotArg => this.evaluate(block.value.expression, values, slotArg)
+ exec: slotArg => {
+ return this.evaluate(block.value.expression, scope.createChildScope(slotArg, block.id));
+ }
};
}
if (block.type.startsWith('fn:')) { // ユーザー関数呼び出し
const fnName = block.type.split(':')[1];
- const fn = this.getVarVal(fnName, values);
+ const fn = scope.getState(fnName);
+ const args = {};
for (let i = 0; i < fn.slots.length; i++) {
const name = fn.slots[i];
- slotArg[name] = this.evaluate(block.args[i], values);
+ args[name] = this.evaluate(block.args[i], scope);
}
- return fn.exec(slotArg);
+ return fn.exec(args);
}
if (block.args === undefined) return null;
@@ -447,8 +480,9 @@ export class AiScript {
for: (times, fn) => {
const result = [];
for (let i = 0; i < times; i++) {
- slotArg[fn.slots[0]] = i + 1;
- result.push(fn.exec(slotArg));
+ result.push(fn.exec({
+ [fn.slots[0]]: i + 1
+ }));
}
return result;
},
@@ -476,40 +510,12 @@ export class AiScript {
};
const fnName = block.type;
-
const fn = funcs[fnName];
if (fn == null) {
- console.error('Unknown function: ' + fnName);
- throw new Error('Unknown function: ' + fnName);
- }
-
- const args = block.args.map(x => this.evaluate(x, values, slotArg));
-
- return fn(...args);
- }
-
- /**
- * 指定した名前の変数の値を取得します
- * @param name 変数名
- * @param values ユーザー定義変数のリスト
- */
- @autobind
- private getVarVal(name: string, values: { name: string, value: any }[]): any {
- const v = values.find(v => v.name === name);
- if (v) {
- return v.value;
- }
-
- const pageVar = this.pageVars.find(v => v.name === name);
- if (pageVar) {
- return pageVar.value;
- }
-
- if (AiScript.envVarsDef[name] !== undefined) {
- return this.envVars[name];
+ throw new AiScriptError(`No such function '${fnName}'`);
+ } else {
+ return fn(...block.args.map(x => this.evaluate(x, scope)));
}
-
- throw new Error(`Script: No such variable '${name}'`);
}
@autobind