diff options
| author | syuilo <syuilotan@yahoo.co.jp> | 2019-05-01 14:54:34 +0900 |
|---|---|---|
| committer | syuilo <syuilotan@yahoo.co.jp> | 2019-05-01 14:54:34 +0900 |
| commit | 52ebf2055e0d6a238796ceacf92a7073bf007127 (patch) | |
| tree | 1dbfe42b002e5611346a6073695ea113b6872942 /src/client/app/common/scripts/aiscript.ts | |
| parent | Improve MisskeyPages (diff) | |
| download | sharkey-52ebf2055e0d6a238796ceacf92a7073bf007127.tar.gz sharkey-52ebf2055e0d6a238796ceacf92a7073bf007127.tar.bz2 sharkey-52ebf2055e0d6a238796ceacf92a7073bf007127.zip | |
Improve AiScript
Diffstat (limited to 'src/client/app/common/scripts/aiscript.ts')
| -rw-r--r-- | src/client/app/common/scripts/aiscript.ts | 134 |
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 |