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 | |
| parent | Improve MisskeyPages (diff) | |
| download | misskey-52ebf2055e0d6a238796ceacf92a7073bf007127.tar.gz misskey-52ebf2055e0d6a238796ceacf92a7073bf007127.tar.bz2 misskey-52ebf2055e0d6a238796ceacf92a7073bf007127.zip | |
Improve AiScript
Diffstat (limited to 'src/client/app/common')
5 files changed, 78 insertions, 72 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 diff --git a/src/client/app/common/views/components/page-editor/page-editor.script-block.vue b/src/client/app/common/views/components/page-editor/page-editor.script-block.vue index 2f78f7de3a..7a3942ec80 100644 --- a/src/client/app/common/views/components/page-editor/page-editor.script-block.vue +++ b/src/client/app/common/views/components/page-editor/page-editor.script-block.vue @@ -25,6 +25,9 @@ <section v-else-if="value.type === 'ref'" class="hpdwcrvs"> <select v-model="value.value"> <option v-for="v in aiScript.getVarsByType(getExpectedType ? getExpectedType() : null).filter(x => x.name !== name)" :value="v.name">{{ v.name }}</option> + <optgroup :label="$t('script.argVariables')"> + <option v-for="v in fnSlots" :value="v.name">{{ v.name }}</option> + </optgroup> <optgroup :label="$t('script.pageVariables')"> <option v-for="v in aiScript.getPageVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option> </optgroup> @@ -33,11 +36,6 @@ </optgroup> </select> </section> - <section v-else-if="value.type === 'in'" class="hpdwcrvs"> - <select v-model="value.value"> - <option v-for="v in fnSlots" :value="v.name">{{ v.name }}</option> - </select> - </section> <section v-else-if="value.type === 'fn'" class="" style="padding:0 16px 16px 16px;"> <ui-textarea v-model="slots"> <span>{{ $t('script.blocks._fn.slots') }}</span> @@ -115,6 +113,7 @@ export default Vue.extend({ }, typeText(): any { if (this.value.type === null) return null; + if (this.value.type.startsWith('fn:')) return this.value.type.split(':')[1]; return this.$t(`script.blocks.${this.value.type}`); }, }, diff --git a/src/client/app/common/views/components/page-editor/page-editor.vue b/src/client/app/common/views/components/page-editor/page-editor.vue index f8959fb0f1..8007970bed 100644 --- a/src/client/app/common/views/components/page-editor/page-editor.vue +++ b/src/client/app/common/views/components/page-editor/page-editor.vue @@ -77,6 +77,7 @@ <template v-if="moreDetails"> <ui-info><span v-html="$t('variables-info2')"></span></ui-info> <ui-info><span v-html="$t('variables-info3')"></span></ui-info> + <ui-info><span v-html="$t('variables-info4')"></span></ui-info> </template> </div> </ui-container> diff --git a/src/client/app/common/views/pages/page/page.if.vue b/src/client/app/common/views/pages/page/page.if.vue index 9dbeaf64fb..417ef0c553 100644 --- a/src/client/app/common/views/pages/page/page.if.vue +++ b/src/client/app/common/views/pages/page/page.if.vue @@ -1,5 +1,5 @@ <template> -<div v-show="script.vars.find(x => x.name === value.var).value"> +<div v-show="script.vars[value.var]"> <x-block v-for="child in value.children" :value="child" :page="page" :script="script" :key="child.id" :h="h"/> </div> </template> diff --git a/src/client/app/common/views/pages/page/page.vue b/src/client/app/common/views/pages/page/page.vue index 88598d3527..27e9a7aec4 100644 --- a/src/client/app/common/views/pages/page/page.vue +++ b/src/client/app/common/views/pages/page/page.vue @@ -27,7 +27,7 @@ import { url } from '../../../../config'; class Script { public aiScript: AiScript; - public vars: any; + public vars: Record<string, any>; constructor(aiScript) { this.aiScript = aiScript; @@ -41,7 +41,7 @@ class Script { public interpolate(str: string) { if (str == null) return null; return str.replace(/\{(.+?)\}/g, match => { - const v = this.vars.find(x => x.name === match.slice(1, -1).trim()).value; + const v = this.vars[match.slice(1, -1).trim()]; return v == null ? 'NULL' : v.toString(); }); } |