From ab3f2afc391ec2a4551933be1c0f5eededbcd83f Mon Sep 17 00:00:00 2001 From: xiaoxustudio Date: Sat, 7 Mar 2026 14:32:27 +0800 Subject: [PATCH 1/7] feat: add scene arguments support --- .../webgal/src/Core/controller/scene/callScene.ts | 13 ++++++++++++- .../src/Core/controller/scene/changeScene.ts | 13 ++++++++++++- .../webgal/src/Core/gameScripts/callSceneScript.ts | 2 +- .../src/Core/gameScripts/changeSceneScript.ts | 2 +- .../webgal/src/Core/gameScripts/choose/index.tsx | 8 ++++---- packages/webgal/src/Core/gameScripts/setVar.ts | 14 +++++++++++++- packages/webgal/src/store/stageInterface.ts | 12 +++++++++++- packages/webgal/src/store/stageReducer.ts | 6 +++++- 8 files changed, 59 insertions(+), 11 deletions(-) diff --git a/packages/webgal/src/Core/controller/scene/callScene.ts b/packages/webgal/src/Core/controller/scene/callScene.ts index ead6e3bd1..60a7c9b62 100644 --- a/packages/webgal/src/Core/controller/scene/callScene.ts +++ b/packages/webgal/src/Core/controller/scene/callScene.ts @@ -6,13 +6,17 @@ import uniqWith from 'lodash/uniqWith'; import { scenePrefetcher } from '@/Core/util/prefetcher/scenePrefetcher'; import { WebGAL } from '@/Core/WebGAL'; +import { arg } from './sceneInterface'; +import { webgalStore } from '@/store/store'; +import { stageActions } from '@/store/stageReducer'; /** * 调用场景 * @param sceneUrl 场景路径 * @param sceneName 场景名称 + * @param args 参数 */ -export const callScene = (sceneUrl: string, sceneName: string) => { +export const callScene = (sceneUrl: string, sceneName: string, args: Array) => { if (WebGAL.sceneManager.lockSceneWrite) { return; } @@ -35,6 +39,13 @@ export const callScene = (sceneUrl: string, sceneName: string) => { scenePrefetcher(subSceneListUniq); logger.debug('现在调用场景,调用结果:', WebGAL.sceneManager.sceneData); WebGAL.sceneManager.lockSceneWrite = false; + // 写入场景调用参数 + webgalStore.dispatch( + stageActions.addSceneArgument({ + url: sceneUrl, + value: args, + }), + ); nextSentence(); }) .catch((e) => { diff --git a/packages/webgal/src/Core/controller/scene/changeScene.ts b/packages/webgal/src/Core/controller/scene/changeScene.ts index 67df0879a..74e414d7c 100644 --- a/packages/webgal/src/Core/controller/scene/changeScene.ts +++ b/packages/webgal/src/Core/controller/scene/changeScene.ts @@ -6,13 +6,17 @@ import uniqWith from 'lodash/uniqWith'; import { scenePrefetcher } from '@/Core/util/prefetcher/scenePrefetcher'; import { WebGAL } from '@/Core/WebGAL'; +import { arg } from './sceneInterface'; +import { stageActions } from '@/store/stageReducer'; +import { webgalStore } from '@/store/store'; /** * 切换场景 * @param sceneUrl 场景路径 * @param sceneName 场景名称 + * @param args 场景参数 */ -export const changeScene = (sceneUrl: string, sceneName: string) => { +export const changeScene = (sceneUrl: string, sceneName: string, args: Array) => { if (WebGAL.sceneManager.lockSceneWrite) { return; } @@ -29,6 +33,13 @@ export const changeScene = (sceneUrl: string, sceneName: string) => { scenePrefetcher(subSceneListUniq); logger.debug('现在切换场景,切换后的结果:', WebGAL.sceneManager.sceneData); WebGAL.sceneManager.lockSceneWrite = false; + // 写入场景调用参数 + webgalStore.dispatch( + stageActions.addSceneArgument({ + url: sceneUrl, + value: args, + }), + ); nextSentence(); }) .catch((e) => { diff --git a/packages/webgal/src/Core/gameScripts/callSceneScript.ts b/packages/webgal/src/Core/gameScripts/callSceneScript.ts index 48aec5494..37fdee983 100644 --- a/packages/webgal/src/Core/gameScripts/callSceneScript.ts +++ b/packages/webgal/src/Core/gameScripts/callSceneScript.ts @@ -9,7 +9,7 @@ import { callScene } from '../controller/scene/callScene'; export const callSceneScript = (sentence: ISentence): IPerform => { const sceneNameArray: Array = sentence.content.split('/'); const sceneName = sceneNameArray[sceneNameArray.length - 1]; - callScene(sentence.content, sceneName); + callScene(sentence.content, sceneName, sentence.args); return { performName: 'none', duration: 0, diff --git a/packages/webgal/src/Core/gameScripts/changeSceneScript.ts b/packages/webgal/src/Core/gameScripts/changeSceneScript.ts index 91a2d17a7..fd9cd8d18 100644 --- a/packages/webgal/src/Core/gameScripts/changeSceneScript.ts +++ b/packages/webgal/src/Core/gameScripts/changeSceneScript.ts @@ -9,7 +9,7 @@ import { changeScene } from '../controller/scene/changeScene'; export const changeSceneScript = (sentence: ISentence): IPerform => { const sceneNameArray: Array = sentence.content.split('/'); const sceneName = sceneNameArray[sceneNameArray.length - 1]; - changeScene(sentence.content, sceneName); + changeScene(sentence.content, sceneName, sentence.args); return { performName: 'none', duration: 0, diff --git a/packages/webgal/src/Core/gameScripts/choose/index.tsx b/packages/webgal/src/Core/gameScripts/choose/index.tsx index b7813c4cc..4ac2b03f3 100644 --- a/packages/webgal/src/Core/gameScripts/choose/index.tsx +++ b/packages/webgal/src/Core/gameScripts/choose/index.tsx @@ -1,4 +1,4 @@ -import { ISentence } from '@/Core/controller/scene/sceneInterface'; +import { arg, ISentence } from '@/Core/controller/scene/sceneInterface'; import { IPerform } from '@/Core/Modules/perform/performInterface'; import { changeScene } from '@/Core/controller/scene/changeScene'; import { jmp } from '@/Core/gameScripts/label/jmp'; @@ -62,7 +62,7 @@ export const choose = (sentence: ISentence): IPerform => { // eslint-disable-next-line react/no-deprecated ReactDOM.render( - + , document.getElementById('chooseContainer'), ); @@ -80,7 +80,7 @@ export const choose = (sentence: ISentence): IPerform => { }; }; -function Choose(props: { chooseOptions: ChooseOption[] }) { +function Choose(props: { chooseOptions: ChooseOption[]; args: Array }) { const font = useFontFamily(); const { playSeEnter, playSeClick } = useSEByWebgalStore(); const applyStyle = useApplyStyle('choose'); @@ -97,7 +97,7 @@ function Choose(props: { chooseOptions: ChooseOption[] }) { ? () => { playSeClick(); if (e.jumpToScene) { - changeScene(e.jump, e.text); + changeScene(e.jump, e.text, props.args); } else { jmp(e.jump); } diff --git a/packages/webgal/src/Core/gameScripts/setVar.ts b/packages/webgal/src/Core/gameScripts/setVar.ts index 75fdfa801..a66c593a6 100644 --- a/packages/webgal/src/Core/gameScripts/setVar.ts +++ b/packages/webgal/src/Core/gameScripts/setVar.ts @@ -12,6 +12,7 @@ import expression from 'angular-expressions'; import get from 'lodash/get'; import random from 'lodash/random'; import { getBooleanArgByKey } from '../util/getSentenceArg'; +import { WebGAL } from '../WebGAL'; /** * 设置变量 @@ -95,11 +96,22 @@ type BaseVal = string | number | boolean | undefined; * 执行函数 */ function EvaluateExpression(val: string) { + const sceneUrl = WebGAL.sceneManager.sceneData.currentScene.sceneUrl; + const sceneArguments = webgalStore.getState().stage.sceneArguments; const instance = expression.compile(val); return instance({ - random: (...args: any[]) => { + // 随机函数 + random(...args: any[]) { return args.length ? random(...args) : Math.random(); }, + // 获取场景调用参数 + getArg(key: string) { + const target = sceneArguments[sceneUrl]; + if (target) { + return target.filter((item) => item.key === key)[0]?.value ?? null; + } + return null; + }, }); } diff --git a/packages/webgal/src/store/stageInterface.ts b/packages/webgal/src/store/stageInterface.ts index 907df19be..d0982b800 100644 --- a/packages/webgal/src/store/stageInterface.ts +++ b/packages/webgal/src/store/stageInterface.ts @@ -1,4 +1,4 @@ -import { ISentence } from '@/Core/controller/scene/sceneInterface'; +import { arg, ISentence } from '@/Core/controller/scene/sceneInterface'; import { BlinkParam, FocusParam } from '@/Core/live2DCore'; /** @@ -237,6 +237,16 @@ export interface IStageState { isDisableTextbox: boolean; replacedUIlable: Record; figureMetaData: figureMetaData; + sceneArguments: ISceneArgument; +} + +export interface ISceneArgument { + [key: string]: Array; +} + +export interface ISceneArgumentPayload { + url: string; + value: Array; } /** diff --git a/packages/webgal/src/store/stageReducer.ts b/packages/webgal/src/store/stageReducer.ts index 57e1ffe42..23871bcb6 100644 --- a/packages/webgal/src/store/stageReducer.ts +++ b/packages/webgal/src/store/stageReducer.ts @@ -13,9 +13,9 @@ import { ILive2DFocus, ILive2DMotion, IRunPerform, + ISceneArgumentPayload, ISetGameVar, ISetStagePayload, - IStageAnimationSetting, IStageState, IUpdateAnimationSettingPayload, } from '@/store/stageInterface'; @@ -74,6 +74,7 @@ export const initState: IStageState = { isDisableTextbox: false, replacedUIlable: {}, figureMetaData: {}, + sceneArguments: {}, }; /** @@ -318,6 +319,9 @@ const stageSlice = createSlice({ state.figureMetaData[action.payload[0]][action.payload[1]] = action.payload[2]; } }, + addSceneArgument: (state, action: PayloadAction) => { + state.sceneArguments[action.payload.url] = action.payload.value; + }, }, }); From def6f4450d246a96c9ad732e35678aa73c6d5d6f Mon Sep 17 00:00:00 2001 From: xiaoxustudio Date: Sat, 7 Mar 2026 16:33:47 +0800 Subject: [PATCH 2/7] refactor: use EvaluateExpression for variable evaluation --- .../controller/gamePlay/scriptExecutor.ts | 23 +--- .../webgal/src/Core/gameScripts/setVar.ts | 114 ++---------------- .../webgal/src/Core/util/evalSentenceFn.ts | 82 +++++++++++++ 3 files changed, 93 insertions(+), 126 deletions(-) create mode 100644 packages/webgal/src/Core/util/evalSentenceFn.ts diff --git a/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts b/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts index 04f5c997b..9a541dfe7 100644 --- a/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts +++ b/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts @@ -4,34 +4,18 @@ import { logger } from '../../util/logger'; import { IStageState } from '@/store/stageInterface'; import { restoreScene } from '../scene/restoreScene'; import { webgalStore } from '@/store/store'; -import { getValueFromStateElseKey } from '@/Core/gameScripts/setVar'; -import { strIf } from '@/Core/controller/gamePlay/strIf'; import { nextSentence } from '@/Core/controller/gamePlay/nextSentence'; import cloneDeep from 'lodash/cloneDeep'; import { ISceneEntry } from '@/Core/Modules/scene'; -import { IBacklogItem } from '@/Core/Modules/backlog'; -import { SYSTEM_CONFIG } from '@/config'; import { WebGAL } from '@/Core/WebGAL'; import { getBooleanArgByKey, getStringArgByKey } from '@/Core/util/getSentenceArg'; +import { EvaluateExpression } from '@/Core/util/evalSentenceFn'; export const whenChecker = (whenValue: string | undefined): boolean => { if (whenValue === undefined) { return true; } - // 先把变量解析出来 - const valExpArr = whenValue.split(/([+\-*\/()>=|<=|==|&&|\|\||!=)/g); - const valExp = valExpArr - .map((_e) => { - const e = _e.trim(); - if (e.match(/[a-zA-Z]/)) { - if (e.match(/^(true|false)$/)) { - return e; - } - return getValueFromStateElseKey(e, true, true); - } else return e; - }) - .reduce((pre, curr) => pre + curr, ''); - return !!strIf(valExp); + return !!EvaluateExpression(whenValue, { ErrorReturnsBoolean: true }); }; /** @@ -62,7 +46,8 @@ export const scriptExecutor = () => { if (contentExp !== null) { contentExp.forEach((e) => { - const contentVarValue = getValueFromStateElseKey(e.replace(/(? { if (sentence.content.match(/\s*=\s*/)) { const key = sentence.content.split(/\s*=\s*/)[0]; const valExp = sentence.content.split(/\s*=\s*/)[1]; - if (/^\s*[a-zA-Z_$][\w$]*\s*\(.*\)\s*$/.test(valExp)) { - webgalStore.dispatch(targetReducerFunction({ key, value: EvaluateExpression(valExp) })); - } else if (valExp.match(/[+\-*\/()]/)) { - // 如果包含加减乘除号,则运算 - // 先取出运算表达式中的变量 - const valExpArr = valExp.split(/([+\-*\/()])/g); - // 将变量替换为变量的值,然后合成表达式字符串 - const valExp2 = valExpArr - .map((e) => { - if (!e.trim().match(/^[a-zA-Z_$][a-zA-Z0-9_.]*$/)) { - // 检查是否是变量名,不是就返回本身 - return e; - } - const _r = getValueFromStateElseKey(e.trim(), true); - return typeof _r === 'string' ? `'${_r}'` : _r; - }) - .reduce((pre, curr) => pre + curr, ''); - let result = ''; - try { - const exp = compile(valExp2); - result = exp(); - } catch (e) { - logger.error('expression compile error', e); - } - webgalStore.dispatch(targetReducerFunction({ key, value: result })); - } else if (valExp.match(/true|false/)) { - if (valExp.match(/true/)) { - webgalStore.dispatch(targetReducerFunction({ key, value: true })); - } - if (valExp.match(/false/)) { - webgalStore.dispatch(targetReducerFunction({ key, value: false })); - } - } else if (valExp.length === 0) { + if (valExp.length === 0) { webgalStore.dispatch(targetReducerFunction({ key, value: '' })); + } else if (!isNaN(Number(valExp))) { + webgalStore.dispatch(targetReducerFunction({ key, value: Number(valExp) })); } else { - if (!isNaN(Number(valExp))) { - webgalStore.dispatch(targetReducerFunction({ key, value: Number(valExp) })); - } else { - // 字符串 - webgalStore.dispatch(targetReducerFunction({ key, value: getValueFromStateElseKey(valExp, true) })); - } + webgalStore.dispatch( + targetReducerFunction({ key, value: EvaluateExpression(valExp, { InvalidValueReturns: 'origin' }) }), + ); } if (setGlobal) { logger.debug('设置全局变量:', { key, value: webgalStore.getState().userData.globalGameVar[key] }); @@ -89,66 +52,3 @@ export const setVar = (sentence: ISentence): IPerform => { stopTimeout: undefined, // 暂时不用,后面会交给自动清除 }; }; - -type BaseVal = string | number | boolean | undefined; - -/** - * 执行函数 - */ -function EvaluateExpression(val: string) { - const sceneUrl = WebGAL.sceneManager.sceneData.currentScene.sceneUrl; - const sceneArguments = webgalStore.getState().stage.sceneArguments; - const instance = expression.compile(val); - return instance({ - // 随机函数 - random(...args: any[]) { - return args.length ? random(...args) : Math.random(); - }, - // 获取场景调用参数 - getArg(key: string) { - const target = sceneArguments[sceneUrl]; - if (target) { - return target.filter((item) => item.key === key)[0]?.value ?? null; - } - return null; - }, - }); -} - -/** - * 取不到时返回 undefined - */ -export function getValueFromState(key: string) { - let ret: any; - const stage = webgalStore.getState().stage; - const userData = webgalStore.getState().userData; - const _Merge = { stage, userData }; // 不要直接合并到一起,防止可能的键冲突 - if (stage.GameVar.hasOwnProperty(key)) { - ret = stage.GameVar[key]; - } else if (userData.globalGameVar.hasOwnProperty(key)) { - ret = userData.globalGameVar[key]; - } else if (key.startsWith('$')) { - const propertyKey = key.replace('$', ''); - ret = get(_Merge, propertyKey, undefined) as BaseVal; - } - return ret; -} - -/** - * 取不到时返回 {key} - */ -export function getValueFromStateElseKey(key: string, useKeyNameAsReturn = false, quoteString = false) { - const valueFromState = getValueFromState(key); - if (valueFromState === null || valueFromState === undefined) { - logger.warn('valueFromState result null, key = ' + key); - if (useKeyNameAsReturn) { - return key; - } - return `{${key}}`; - } - // 用 "" 包裹字符串,用于使用 compile 条件判断,处理字符串类型的变量 - if (quoteString && typeof valueFromState === 'string') { - return `"${valueFromState.replaceAll('"', '\\"')}"`; - } - return valueFromState; -} diff --git a/packages/webgal/src/Core/util/evalSentenceFn.ts b/packages/webgal/src/Core/util/evalSentenceFn.ts new file mode 100644 index 000000000..d5cddb9c9 --- /dev/null +++ b/packages/webgal/src/Core/util/evalSentenceFn.ts @@ -0,0 +1,82 @@ +import { webgalStore } from '@/store/store'; +import { random } from 'lodash'; +import { WebGAL } from '../WebGAL'; +import expression from 'angular-expressions'; +import { logger } from '@/Core/util/logger'; + +// 是否是函数调用 +export const isFunctionCall = (valExp: string) => { + return /^\s*[a-zA-Z_$][\w$]*\s*\(.*\)\s*$/.test(valExp); +}; + +export interface EvaluateExpressionOptions { + /** + * 当是无效值 `null | undefined | 报错` 时返回原值还是返回{...}包裹 + * @default block {...}包裹 + */ + InvalidValueReturns?: 'origin' | 'block'; + /** + * 当是表达式报错时,是否返回布尔值 + */ + ErrorReturnsBoolean?: boolean; +} + +/** + * 执行运行时表达式 + */ +export const EvaluateExpression = (val: string, options: EvaluateExpressionOptions = {}) => { + const sceneUrl = WebGAL.sceneManager.sceneData.currentScene.sceneUrl; + const sceneArguments = webgalStore.getState().stage.sceneArguments; + const stage = webgalStore.getState().stage; + const userData = webgalStore.getState().userData; + const globalVars = userData.globalGameVar; + const localVars = stage.GameVar; + const _Merge = { $stage: stage, $userData: userData }; // 不要直接合并到一起,防止可能的键冲突 + try { + const instance = expression.compile(val); + const evalResult = instance({ + // 注入变量 + ...globalVars, + ...localVars, + ..._Merge, + // 随机函数 + random(...args: any[]) { + return args.length ? random(...args) : Math.random(); + }, + // 获取场景调用参数 + getArg(key: string) { + const target = sceneArguments[sceneUrl]; + if (target) { + return target.filter((item) => item.key === key)[0]?.value ?? null; + } + return null; + }, + }); + + if ((evalResult === null || evalResult === undefined) && options) { + switch (options.InvalidValueReturns) { + case 'block': + return `{${val}}`; + case 'origin': + return val; + default: + return evalResult; + } + } + + return evalResult; + } catch { + logger.warn('EvaluateExpression throw error, expr = ' + val); + if (options.ErrorReturnsBoolean) { + return false; + } + switch (options.InvalidValueReturns) { + case 'block': + return `{${val}}`; + case 'origin': + return val; + default: + return `{${val}}`; + } + } +}; From a0df80e156abe890c17fd83ecd2abb9bf3607567 Mon Sep 17 00:00:00 2001 From: xiaoxustudio Date: Sat, 7 Mar 2026 16:44:21 +0800 Subject: [PATCH 3/7] chore: remove strIf file --- packages/webgal/src/Core/controller/gamePlay/strIf.ts | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 packages/webgal/src/Core/controller/gamePlay/strIf.ts diff --git a/packages/webgal/src/Core/controller/gamePlay/strIf.ts b/packages/webgal/src/Core/controller/gamePlay/strIf.ts deleted file mode 100644 index 82f7d87f1..000000000 --- a/packages/webgal/src/Core/controller/gamePlay/strIf.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { compile } from 'angular-expressions'; - -export function strIf(s: string) { - try { - const res = compile(s); - return res(); - } catch { - return false; - } -} From abf6243cd262b810dce8e7aaa634a178c5fec9a0 Mon Sep 17 00:00:00 2001 From: xiaoxustudio Date: Sat, 7 Mar 2026 16:48:25 +0800 Subject: [PATCH 4/7] chore: change the filter function to the find function --- packages/webgal/src/Core/util/evalSentenceFn.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webgal/src/Core/util/evalSentenceFn.ts b/packages/webgal/src/Core/util/evalSentenceFn.ts index d5cddb9c9..d0017ccab 100644 --- a/packages/webgal/src/Core/util/evalSentenceFn.ts +++ b/packages/webgal/src/Core/util/evalSentenceFn.ts @@ -47,7 +47,7 @@ export const EvaluateExpression = (val: string, options: EvaluateExpressionOptio getArg(key: string) { const target = sceneArguments[sceneUrl]; if (target) { - return target.filter((item) => item.key === key)[0]?.value ?? null; + return target.find((item) => item.key === key)?.value ?? null; } return null; }, From 12bc1c7bc8afe1dc58e011fae53feef0c4b69695 Mon Sep 17 00:00:00 2001 From: xiaoxustudio Date: Sun, 8 Mar 2026 12:56:28 +0800 Subject: [PATCH 5/7] update: upgrade angular-expressions to 1.5.1 --- packages/webgal/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/webgal/package.json b/packages/webgal/package.json index 892349f77..059b6ba76 100644 --- a/packages/webgal/package.json +++ b/packages/webgal/package.json @@ -12,7 +12,7 @@ "@emotion/css": "^11.11.2", "@icon-park/react": "^1.4.2", "@reduxjs/toolkit": "^1.8.1", - "angular-expressions": "^1.4.3", + "angular-expressions": "^1.5.1", "axios": "^0.30.2", "cloudlogjs": "^1.0.9", "gifuct-js": "^2.1.2", diff --git a/yarn.lock b/yarn.lock index 062e2199d..a9d3b1a5d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1426,10 +1426,10 @@ ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -angular-expressions@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/angular-expressions/-/angular-expressions-1.4.3.tgz#24fb9e8e9e0278885dd0f7e28512ac88a74037f6" - integrity sha512-r7j+dqOuHy0OYiR5AazDixU/Us3TDN2FfuxGX4Dq6d61Y2MhBQHMdUNBfkkLPjDqVm2Is394h31gC3bcBwy9zw== +angular-expressions@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/angular-expressions/-/angular-expressions-1.5.1.tgz#32aa451c4bd4bc589efc031ed2aae9bf53d4e455" + integrity sha512-Ukcyuye0eb15zuvMFR7Kziuf7gV0gYAZXMevNI40rXF8f9k+/yk8+r5edFWRFOwnqSvAEFoP2bKNXaXgYa+Ayw== ansi-escapes@^4.3.0: version "4.3.2" From a185082417c13e2a167a99727ab5ee7842f8cd97 Mon Sep 17 00:00:00 2001 From: xiaoxustudio Date: Sun, 8 Mar 2026 15:00:24 +0800 Subject: [PATCH 6/7] fix: phonetic grammar --- packages/webgal/src/Stage/TextBox/TextBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webgal/src/Stage/TextBox/TextBox.tsx b/packages/webgal/src/Stage/TextBox/TextBox.tsx index dea485b2d..0dd3e4c9c 100644 --- a/packages/webgal/src/Stage/TextBox/TextBox.tsx +++ b/packages/webgal/src/Stage/TextBox/TextBox.tsx @@ -265,7 +265,7 @@ interface Segment { } function parseString(input: string): Segment[] { - const regex = /(\[(.*?)\]\((.*?)\))|([^\[\]]+)/g; + const regex = /(\[([^\]]+)\]\(([^)]+)\))|([\s\S]+?(?=\[|$))/g; const result: Segment[] = []; let match: RegExpExecArray | null; From c8a0c77da38296f63f06d140aae7272d1796d220 Mon Sep 17 00:00:00 2001 From: xiaoxustudio Date: Sun, 14 Jun 2026 23:23:57 +0800 Subject: [PATCH 7/7] refactor: optimize the parameter passing in the scene and the evaluation of expressions --- .../parser/src/interface/runtimeInterface.ts | 4 +- .../parser/src/interface/sceneInterface.ts | 2 +- packages/webgal/package.json | 2 +- packages/webgal/src/Core/Modules/scene.ts | 3 + .../src/Core/Modules/stage/stageInterface.ts | 12 +- .../controller/gamePlay/scriptExecutor.ts | 16 ++- .../src/Core/controller/scene/callScene.ts | 18 ++- .../src/Core/controller/scene/changeScene.ts | 13 +- .../src/Core/controller/scene/restoreScene.ts | 1 + .../src/Core/gameScripts/callSceneScript.ts | 7 +- .../src/Core/gameScripts/changeSceneScript.ts | 9 +- .../src/Core/gameScripts/choose/index.tsx | 30 +++-- .../webgal/src/Core/gameScripts/setVar.ts | 100 +--------------- .../webgal/src/Core/util/evalSentenceFn.ts | 113 ++++++++++++------ yarn.lock | 8 +- 15 files changed, 159 insertions(+), 179 deletions(-) diff --git a/packages/parser/src/interface/runtimeInterface.ts b/packages/parser/src/interface/runtimeInterface.ts index 981bd5754..ebe7033ed 100644 --- a/packages/parser/src/interface/runtimeInterface.ts +++ b/packages/parser/src/interface/runtimeInterface.ts @@ -12,4 +12,6 @@ export interface sceneEntry { * 场景栈条目接口 (兼容性别名) * @interface ISceneEntry */ -export interface ISceneEntry extends sceneEntry {} +export interface ISceneEntry extends sceneEntry { + sceneParams: Record; // 场景参数 +} diff --git a/packages/parser/src/interface/sceneInterface.ts b/packages/parser/src/interface/sceneInterface.ts index 1b05024c8..16d9caff6 100644 --- a/packages/parser/src/interface/sceneInterface.ts +++ b/packages/parser/src/interface/sceneInterface.ts @@ -1,7 +1,7 @@ /** * 语句类型 */ -import { sceneEntry, ISceneEntry } from './runtimeInterface'; +import { ISceneEntry } from './runtimeInterface'; import { fileType } from './assets'; export enum commandType { diff --git a/packages/webgal/package.json b/packages/webgal/package.json index 6e9356e65..9a1c2de7a 100644 --- a/packages/webgal/package.json +++ b/packages/webgal/package.json @@ -12,7 +12,7 @@ "@emotion/css": "^11.11.2", "@icon-park/react": "^1.4.2", "@reduxjs/toolkit": "^1.8.1", - "angular-expressions": "^1.5.1", + "angular-expressions": "^1.5.5", "axios": "^1.13.5", "cloudlogjs": "^1.0.9", "gifuct-js": "^2.1.2", diff --git a/packages/webgal/src/Core/Modules/scene.ts b/packages/webgal/src/Core/Modules/scene.ts index 10c4f7bd4..bf0bac445 100644 --- a/packages/webgal/src/Core/Modules/scene.ts +++ b/packages/webgal/src/Core/Modules/scene.ts @@ -5,6 +5,7 @@ export interface ISceneEntry { sceneName: string; // 场景名称 sceneUrl: string; // 场景url continueLine: number; // 继续原场景的行号 + sceneParams: Record; // 场景参数 } /** @@ -29,6 +30,7 @@ export class SceneManager { public sceneData: ISceneData = cloneDeep(initSceneData); public lockSceneWrite = false; public sceneWritePromise: Promise | null = null; + public currentSceneParams: Record = {}; // 当前场景参数 public resetScene() { this.sceneData.currentSentenceId = 0; @@ -37,5 +39,6 @@ export class SceneManager { this.sceneWritePromise = null; this.settledScenes.clear(); this.settledAssets.clear(); + this.currentSceneParams = {}; } } diff --git a/packages/webgal/src/Core/Modules/stage/stageInterface.ts b/packages/webgal/src/Core/Modules/stage/stageInterface.ts index 3f86b00a8..4e8cd25f5 100644 --- a/packages/webgal/src/Core/Modules/stage/stageInterface.ts +++ b/packages/webgal/src/Core/Modules/stage/stageInterface.ts @@ -1,4 +1,4 @@ -import { arg, ISentence } from '@/Core/controller/scene/sceneInterface'; +import { ISentence } from '@/Core/controller/scene/sceneInterface'; import { BlinkParam, FocusParam } from '@/Core/live2DCore'; /** @@ -253,16 +253,6 @@ export interface IStageState { isDisableTextbox: boolean; replacedUIlable: Record; figureMetaData: figureMetaData; - sceneArguments: ISceneArgument; -} - -export interface ISceneArgument { - [key: string]: Array; -} - -export interface ISceneArgumentPayload { - url: string; - value: Array; } /** diff --git a/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts b/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts index 9b94d06f5..a54d36668 100644 --- a/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts +++ b/packages/webgal/src/Core/controller/gamePlay/scriptExecutor.ts @@ -10,15 +10,15 @@ import { getBooleanArgByKey, getStringArgByKey } from '@/Core/util/getSentenceAr import { stageStateManager } from '@/Core/Modules/stage/stageStateManager'; import { jumpToLabel } from '@/Core/gameScripts/label/jumpToLabel'; import { prefetchCurrentSceneByProgress } from '@/Core/util/prefetcher/progressPrefetcher'; +import { evaluateStageExpression } from '@/Core/util/evalSentenceFn'; const MAX_FORWARD_SCRIPT_EXECUTION = 1000; -import { EvaluateExpression } from '@/Core/util/evalSentenceFn'; export const whenChecker = (whenValue: string | undefined): boolean => { if (whenValue === undefined) { return true; } - return !!EvaluateExpression(whenValue, { ErrorReturnsBoolean: true }); + return evaluateStageExpression(whenValue, { returnType: 'boolean' }); }; /** @@ -55,9 +55,15 @@ export const scriptExecutor = (depth = 0) => { if (contentExp !== null) { contentExp.forEach((e) => { - const likeExpr = e.replace(/(?) => { +export const callScene = (sceneUrl: string, sceneName: string, params: Record = {}) => { if (WebGAL.sceneManager.lockSceneWrite) { return; } @@ -27,6 +25,7 @@ export const callScene = (sceneUrl: string, sceneName: string, args: Array) sceneName: WebGAL.sceneManager.sceneData.currentScene.sceneName, sceneUrl: WebGAL.sceneManager.sceneData.currentScene.sceneUrl, continueLine: WebGAL.sceneManager.sceneData.currentSentenceId, + sceneParams: cloneDeep(WebGAL.sceneManager.currentSceneParams), // 保存当前场景参数 }); // 场景写入到运行时 const sceneWritePromise = sceneFetcher(sceneUrl) @@ -37,6 +36,15 @@ export const callScene = (sceneUrl: string, sceneName: string, args: Array) WebGAL.sceneManager.settledScenes.add(sceneUrl); // 放入已加载场景列表,避免递归加载相同场景 logger.debug('现在调用场景,调用结果:', WebGAL.sceneManager.sceneData); shouldAutoNext = !isFastPreviewSceneWrite; + WebGAL.sceneManager.currentSceneParams = Object.entries(params) + .map(([key, value]) => ({ + key, + value: evaluateStageExpressionWithoutDot(value), + })) + .reduce((res: Record, item: Record) => { + res[item.key] = item.value; + return res; + }, {} as Record); // 设置新场景参数并立即求值 }) .catch((e) => { logger.error('场景调用错误', e); diff --git a/packages/webgal/src/Core/controller/scene/changeScene.ts b/packages/webgal/src/Core/controller/scene/changeScene.ts index 6c67be8d1..7fe68e291 100644 --- a/packages/webgal/src/Core/controller/scene/changeScene.ts +++ b/packages/webgal/src/Core/controller/scene/changeScene.ts @@ -5,7 +5,7 @@ import { nextSentence } from '@/Core/controller/gamePlay/nextSentence'; import { clearPrefetchLinks } from '@/Core/util/prefetcher/assetsPrefetcher'; import { WebGAL } from '@/Core/WebGAL'; -import { arg } from './sceneInterface'; +import { evaluateStageExpressionWithoutDot } from '@/Core/util/evalSentenceFn'; /** * 切换场景 @@ -13,7 +13,7 @@ import { arg } from './sceneInterface'; * @param sceneName 场景名称 * @param args 场景参数 */ -export const changeScene = (sceneUrl: string, sceneName: string, args: Array) => { +export const changeScene = (sceneUrl: string, sceneName: string, params: Record = {}) => { if (WebGAL.sceneManager.lockSceneWrite) { return; } @@ -29,6 +29,15 @@ export const changeScene = (sceneUrl: string, sceneName: string, args: Array ({ + key, + value: evaluateStageExpressionWithoutDot(value), + })) + .reduce((res: Record, item: Record) => { + res[item.key] = item.value; + return res; + }, {} as Record); // 设置新场景参数并立即求值 }) .catch((e) => { logger.error('场景调用错误', e); diff --git a/packages/webgal/src/Core/controller/scene/restoreScene.ts b/packages/webgal/src/Core/controller/scene/restoreScene.ts index d1d671e6a..407b1ea8a 100644 --- a/packages/webgal/src/Core/controller/scene/restoreScene.ts +++ b/packages/webgal/src/Core/controller/scene/restoreScene.ts @@ -24,6 +24,7 @@ export const restoreScene = (entry: ISceneEntry) => { WebGAL.sceneManager.sceneData.currentSentenceId = entry.continueLine + 1; // 重设场景 logger.debug('现在恢复场景,恢复后场景:', WebGAL.sceneManager.sceneData.currentScene); shouldAutoNext = !isFastPreviewSceneWrite; + WebGAL.sceneManager.currentSceneParams = entry.sceneParams ?? {}; }) .catch((e) => { logger.error('场景调用错误', e); diff --git a/packages/webgal/src/Core/gameScripts/callSceneScript.ts b/packages/webgal/src/Core/gameScripts/callSceneScript.ts index e7f9a5848..6b37de637 100644 --- a/packages/webgal/src/Core/gameScripts/callSceneScript.ts +++ b/packages/webgal/src/Core/gameScripts/callSceneScript.ts @@ -9,6 +9,11 @@ import { callScene } from '../controller/scene/callScene'; export const callSceneScript = (sentence: ISentence): IPerform => { const sceneNameArray: Array = sentence.content.split('/'); const sceneName = sceneNameArray[sceneNameArray.length - 1]; - callScene(sentence.content, sceneName); + // 从 args 中提取场景参数 + const params: Record = {}; + sentence.args.forEach((arg) => { + if (arg.key.startsWith('@')) params[arg.key.slice(1)] = arg.value; + }); + callScene(sentence.content, sceneName, params); return createNonePerform({ isHoldOn: true }); }; diff --git a/packages/webgal/src/Core/gameScripts/changeSceneScript.ts b/packages/webgal/src/Core/gameScripts/changeSceneScript.ts index ed50f8fb2..0c4073b89 100644 --- a/packages/webgal/src/Core/gameScripts/changeSceneScript.ts +++ b/packages/webgal/src/Core/gameScripts/changeSceneScript.ts @@ -9,6 +9,13 @@ import { changeScene } from '../controller/scene/changeScene'; export const changeSceneScript = (sentence: ISentence): IPerform => { const sceneNameArray: Array = sentence.content.split('/'); const sceneName = sceneNameArray[sceneNameArray.length - 1]; - changeScene(sentence.content, sceneName); + // 从 args 中提取场景参数 + const params: Record = {}; + sentence.args.forEach((arg) => { + if (arg.key.startsWith('@')) { + params[arg.key.slice(1)] = arg.value; + } + }); + changeScene(sentence.content, sceneName, params); return createNonePerform({ isHoldOn: true }); }; diff --git a/packages/webgal/src/Core/gameScripts/choose/index.tsx b/packages/webgal/src/Core/gameScripts/choose/index.tsx index f37fbaa53..99b91f76a 100644 --- a/packages/webgal/src/Core/gameScripts/choose/index.tsx +++ b/packages/webgal/src/Core/gameScripts/choose/index.tsx @@ -1,9 +1,8 @@ -import { arg, ISentence } from '@/Core/controller/scene/sceneInterface'; +import { ISentence } from '@/Core/controller/scene/sceneInterface'; import { createNonePerform, IPerform } from '@/Core/Modules/perform/performInterface'; import { changeScene } from '@/Core/controller/scene/changeScene'; import { jmp } from '@/Core/gameScripts/label/jmp'; import ReactDOM from 'react-dom'; -import React from 'react'; import styles from './choose.module.scss'; import { webgalStore } from '@/store/store'; import { useSEByWebgalStore } from '@/hooks/useSoundEffect'; @@ -16,6 +15,7 @@ import { useFontFamily } from '@/hooks/useFontFamily'; import { getNumberArgByKey } from '@/Core/util/getSentenceArg'; class ChooseOption { + public params: Record = {}; /** * 格式: * (showConditionVar>1)[enableConditionVar>2]->text:jump @@ -38,6 +38,13 @@ class ChooseOption { } return option; } + public appendArgs(args: { key: string; value: any }[]) { + // 解析后面的 -xxx 参数 + args.forEach(({ key, value }) => { + if (key.startsWith('@')) this.params[key.slice(1)] = value; + }); + return this; + } public text: string; public jump: string; public jumpToScene: boolean; @@ -55,14 +62,13 @@ class ChooseOption { * 显示选择枝 * @param sentence */ -export const choose = (sentence: ISentence, args: any): IPerform => { +export const choose = (sentence: ISentence): IPerform => { const chooseOptionScripts = sentence.content.split(/(? ChooseOption.parse(e.trim())); + const chooseOptions = chooseOptionScripts.map((e) => ChooseOption.parse(e.trim()).appendArgs(sentence.args)); const defaultChoose = getNumberArgByKey(sentence, 'defaultChoose'); const defaultPreviewChoice = getDefaultPreviewChoice(chooseOptions, defaultChoose); - if (defaultPreviewChoice) { - selectChooseOption(defaultPreviewChoice, false, args); + selectChooseOption(defaultPreviewChoice, false); if (!defaultPreviewChoice.jumpToScene) { // The default preview choice is resolved during script calculation. // Let scriptExecutor continue from the target label in this same forward. @@ -79,7 +85,7 @@ export const choose = (sentence: ISentence, args: any): IPerform => { // eslint-disable-next-line react/no-deprecated ReactDOM.render( - + , document.getElementById('chooseContainer'), ); @@ -110,22 +116,22 @@ function getDefaultPreviewChoice(chooseOptions: ChooseOption[], defaultChoose: n return defaultOption; } -function selectChooseOption(option: ChooseOption, autoNext = true, args: arg[]) { +function selectChooseOption(option: ChooseOption, autoNext = true) { if (option.jumpToScene) { - changeScene(option.jump, option.text, args); + changeScene(option.jump, option.text, option.params); } else { jmp(option.jump, autoNext); } } -function Choose(props: { chooseOptions: ChooseOption[]; args: arg[] }) { +function Choose(props: { chooseOptions: ChooseOption[] }) { const font = useFontFamily(); const { playSeEnter, playSeClick } = useSEByWebgalStore(); const applyStyle = useApplyStyle('choose'); // 运行时计算JSX.Element[] const runtimeBuildList = (chooseListFull: ChooseOption[]) => { return chooseListFull - .filter((e, i) => whenChecker(e.showCondition)) + .filter((e) => whenChecker(e.showCondition)) .map((e, i) => { const enable = whenChecker(e.enableCondition); const className = enable @@ -135,7 +141,7 @@ function Choose(props: { chooseOptions: ChooseOption[]; args: arg[] }) { ? () => { playSeClick(); WebGAL.gameplay.performController.unmountPerform('choose'); - selectChooseOption(e, true, props.args); + selectChooseOption(e, true); } : () => {}; return ( diff --git a/packages/webgal/src/Core/gameScripts/setVar.ts b/packages/webgal/src/Core/gameScripts/setVar.ts index 53f32ade3..e691dd6d1 100644 --- a/packages/webgal/src/Core/gameScripts/setVar.ts +++ b/packages/webgal/src/Core/gameScripts/setVar.ts @@ -6,11 +6,8 @@ import { setScriptManagedGlobalVar } from '@/store/userDataReducer'; import { ISetGameVar } from '@/Core/Modules/stage/stageInterface'; import { dumpToStorageFast } from '@/Core/controller/storage/storageController'; import { getBooleanArgByKey } from '../util/getSentenceArg'; -import expression from 'angular-expressions'; -import get from 'lodash/get'; -import random from 'lodash/random'; import { stageStateManager } from '@/Core/Modules/stage/stageStateManager'; -import { compile } from 'angular-expressions'; +import { evaluateStageExpressionWithoutDot } from '../util/evalSentenceFn'; interface ISetGameVarFromExpressionPayload { key: string; @@ -40,7 +37,7 @@ export const setGameVarFromExpression = ({ if (!normalizedKey) { return; } - setGameVar({ key: normalizedKey, value: resolveSetVarValue(value) }); + setGameVar({ key: normalizedKey, value: evaluateStageExpressionWithoutDot(value, { returnType: 'origin' }) }); if (isGlobal) { logger.debug('设置全局变量:', { key: normalizedKey, @@ -70,96 +67,3 @@ export const setVar = (sentence: ISentence): IPerform => { } return createNonePerform(); }; - -type BaseVal = string | number | boolean | undefined; - -export function resolveSetVarValue(valExp: string): string | boolean | number { - if (/^\s*[a-zA-Z_$][\w$]*\s*\(.*\)\s*$/.test(valExp)) { - return EvaluateExpression(valExp); - } else if (valExp.match(/[+\-*\/()]/)) { - const valExpArr = valExp.split(/([+\-*\/()])/g); - const valExp2 = valExpArr - .map((e) => { - if (!e.trim().match(/^[a-zA-Z_$][a-zA-Z0-9_.]*$/)) { - return e; - } - const _r = getValueFromStateElseKey(e.trim(), true); - return typeof _r === 'string' ? `'${_r}'` : _r; - }) - .reduce((pre, curr) => pre + curr, ''); - let result = ''; - try { - const exp = compile(valExp2); - result = exp(); - } catch (e) { - logger.error('expression compile error', e); - } - return result; - } else if (valExp.match(/true|false/)) { - if (valExp.match(/true/)) { - return true; - } - if (valExp.match(/false/)) { - return false; - } - } else if (valExp.length === 0) { - return ''; - } else { - if (!isNaN(Number(valExp))) { - return Number(valExp); - } else { - return getValueFromStateElseKey(valExp, true) ?? ''; - } - } - return ''; -} - -/** - * 执行函数 - */ -function EvaluateExpression(val: string) { - const instance = expression.compile(val); - return instance({ - random: (...args: any[]) => { - return args.length ? random(...args) : Math.random(); - }, - }); -} - -/** - * 取不到时返回 undefined - */ -export function getValueFromState(key: string) { - let ret: any; - const stage = stageStateManager.getCalculationStageState(); - const userData = webgalStore.getState().userData; - const _Merge = { stage, userData }; // 不要直接合并到一起,防止可能的键冲突 - if (stage.GameVar.hasOwnProperty(key)) { - ret = stage.GameVar[key]; - } else if (userData.globalGameVar.hasOwnProperty(key)) { - ret = userData.globalGameVar[key]; - } else if (key.startsWith('$')) { - const propertyKey = key.replace('$', ''); - ret = get(_Merge, propertyKey, undefined) as BaseVal; - } - return ret; -} - -/** - * 取不到时返回 {key} - */ -export function getValueFromStateElseKey(key: string, useKeyNameAsReturn = false, quoteString = false) { - const valueFromState = getValueFromState(key); - if (valueFromState === null || valueFromState === undefined) { - logger.warn('valueFromState result null, key = ' + key); - if (useKeyNameAsReturn) { - return key; - } - return `{${key}}`; - } - // 用 "" 包裹字符串,用于使用 compile 条件判断,处理字符串类型的变量 - if (quoteString && typeof valueFromState === 'string') { - return `"${valueFromState.replaceAll('"', '\\"')}"`; - } - return valueFromState; -} diff --git a/packages/webgal/src/Core/util/evalSentenceFn.ts b/packages/webgal/src/Core/util/evalSentenceFn.ts index d0017ccab..66a5da972 100644 --- a/packages/webgal/src/Core/util/evalSentenceFn.ts +++ b/packages/webgal/src/Core/util/evalSentenceFn.ts @@ -1,33 +1,60 @@ import { webgalStore } from '@/store/store'; -import { random } from 'lodash'; +import random from 'lodash/random'; import { WebGAL } from '../WebGAL'; import expression from 'angular-expressions'; import { logger } from '@/Core/util/logger'; +import { stageStateManager } from '../Modules/stage/stageStateManager'; // 是否是函数调用 export const isFunctionCall = (valExp: string) => { return /^\s*[a-zA-Z_$][\w$]*\s*\(.*\)\s*$/.test(valExp); }; +export const isObject = (valExp: string) => { + try { + return new Function(`return ${valExp}`)(); + } catch { + return false; + } +}; + export interface EvaluateExpressionOptions { /** - * 当是无效值 `null | undefined | 报错` 时返回原值还是返回{...}包裹 - * @default block {...}包裹 - */ - InvalidValueReturns?: 'origin' | 'block'; - /** - * 当是表达式报错时,是否返回布尔值 + * 当是无效值 `null | undefined | Error` 时返回类型(函数调用时无效,将返回求值结果) + * @default `result` + * @description + * `result` 返回求值结果(报错时无效) + * `origin` 返回原expr值 + * `block` 返回 {...} 包裹原值 + * `boolean` 返回 false */ - ErrorReturnsBoolean?: boolean; + returnType?: 'resullt' | 'origin' | 'block' | 'boolean'; } /** - * 执行运行时表达式 + * 在当前`Stage`运行时执行表达式 + * + * 可执行:基础表达式,函数调用,变量调用,`{...}`包裹 + * @description + * 当`expr`不为string时,将直接返回`expr` + * @param val 表达式 + * @param options 配置 */ -export const EvaluateExpression = (val: string, options: EvaluateExpressionOptions = {}) => { - const sceneUrl = WebGAL.sceneManager.sceneData.currentScene.sceneUrl; - const sceneArguments = webgalStore.getState().stage.sceneArguments; - const stage = webgalStore.getState().stage; +export const evaluateStageExpression = ( + expr: string | number | boolean, + options: EvaluateExpressionOptions = { + returnType: 'resullt', + }, +) => { + if (typeof expr === 'number' || typeof expr === 'boolean') return expr; + let val = expr.trim(); + if (val.startsWith('{') && val.endsWith('}')) { + if (!isObject(val)) { + val = val.slice(1, -1); + } + } + const sceneArguments = WebGAL.sceneManager.currentSceneParams; + const stage = stageStateManager.getCalculationStageState(); const userData = webgalStore.getState().userData; const globalVars = userData.globalGameVar; const localVars = stage.GameVar; @@ -35,48 +62,60 @@ export const EvaluateExpression = (val: string, options: EvaluateExpressionOptio try { const instance = expression.compile(val); const evalResult = instance({ - // 注入变量 + /* 内置变量 */ ...globalVars, ...localVars, ..._Merge, - // 随机函数 + /* 内置函数 */ random(...args: any[]) { return args.length ? random(...args) : Math.random(); }, // 获取场景调用参数 - getArg(key: string) { - const target = sceneArguments[sceneUrl]; - if (target) { - return target.find((item) => item.key === key)?.value ?? null; - } - return null; + getParentParams(key: string) { + return sceneArguments[key]; }, }); - if ((evalResult === null || evalResult === undefined) && options) { - switch (options.InvalidValueReturns) { - case 'block': - return `{${val}}`; + if (evalResult === null || evalResult === undefined) { + if (isFunctionCall(val)) return evalResult; + switch (options.returnType) { case 'origin': return val; - default: - return evalResult; + case 'boolean': + return false; + case 'block': + return `{${val}}`; } } - return evalResult; - } catch { - logger.warn('EvaluateExpression throw error, expr = ' + val); - if (options.ErrorReturnsBoolean) { - return false; - } - switch (options.InvalidValueReturns) { - case 'block': - return `{${val}}`; + } catch (e) { + logger.warn('evaluateExpression throw error, expr = ' + val + ', error = ' + e); + switch (options.returnType) { case 'origin': return val; - default: + case 'boolean': + return false; + case 'block': return `{${val}}`; } } }; + +type ESEParameters = Parameters; + +/** + * 无引号字符串求值 + * + * 用于`设置变量,参数处理`处理 + */ +export const evaluateStageExpressionWithoutDot = ( + expr: ESEParameters[0], + op: ESEParameters[1] = { returnType: 'block' }, +) => { + let val = expr; + // 当expr没有标点符号,运算符时,将作为字符串处理 + if (typeof val === 'string' && !/[a-zA-Z_$][\w$]*\s*\(.*\)\s*$/.test(val) && !/[.,<>;"'{}():+\-*/%?![\]]/.test(val)) { + val = `'${expr}'`; + } + return evaluateStageExpression(val, op); +}; diff --git a/yarn.lock b/yarn.lock index 47854e3e1..52bc5a762 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1718,10 +1718,10 @@ ajv@^6.14.0: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -angular-expressions@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/angular-expressions/-/angular-expressions-1.5.1.tgz#32aa451c4bd4bc589efc031ed2aae9bf53d4e455" - integrity sha512-Ukcyuye0eb15zuvMFR7Kziuf7gV0gYAZXMevNI40rXF8f9k+/yk8+r5edFWRFOwnqSvAEFoP2bKNXaXgYa+Ayw== +angular-expressions@^1.5.5: + version "1.5.5" + resolved "https://registry.npmmirror.com/angular-expressions/-/angular-expressions-1.5.5.tgz#26de781221bfe2ad69111e95202b2955bd63f4ee" + integrity sha512-MZqPMw7FYq6XkqE/v4udnanaIRrhJ22hmn3SeJkqDYEtZsoewafa+QgZqR9HrCXP114jaChzOjCjrM6LqvnflA== ansi-escapes@^4.3.0: version "4.3.2"