Update variables UI and parsing

This commit is contained in:
2026-02-01 02:55:43 +08:00
parent bcf664e9a0
commit 5dd9fb6f97
3 changed files with 137 additions and 51 deletions

View File

@@ -26,27 +26,95 @@ export function computeStateSignature(text) {
}
/**
* 解析 <state> 块内容 -> ops[]
* 单行支持运算符,多行只支持覆盖 setYAML
* 解析 <state> 块
* 返回: { rules: [{path, rule}], ops: [{path, op, value, ...}] }
*/
export function parseStateBlock(content) {
const results = [];
const lines = String(content ?? '').split(/\r?\n/);
const rules = [];
const dataLines = [];
// 第一遍:分离规则行和数据行
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
// 规则行:以 $ 开头
if (trimmed.startsWith('$')) {
const parsed = parseRuleLineInternal(trimmed);
if (parsed) rules.push(parsed);
} else {
dataLines.push(line);
}
}
// 第二遍:解析数据
const ops = parseDataLines(dataLines);
return { rules, ops };
}
function parseRuleLineInternal(line) {
const tokens = line.trim().split(/\s+/);
const directives = [];
let pathStart = 0;
for (let i = 0; i < tokens.length; i++) {
if (tokens[i].startsWith('$')) {
directives.push(tokens[i]);
pathStart = i + 1;
} else {
break;
}
}
const path = tokens.slice(pathStart).join(' ').trim();
if (!path || !directives.length) return null;
const rule = {};
for (const tok of directives) {
if (tok === '$ro') { rule.ro = true; continue; }
if (tok === '$lock') { rule.lock = true; continue; }
const rangeMatch = tok.match(/^\$range=\[\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\]$/);
if (rangeMatch) {
rule.min = Math.min(Number(rangeMatch[1]), Number(rangeMatch[2]));
rule.max = Math.max(Number(rangeMatch[1]), Number(rangeMatch[2]));
continue;
}
const stepMatch = tok.match(/^\$step=(\d+(?:\.\d+)?)$/);
if (stepMatch) { rule.step = Math.abs(Number(stepMatch[1])); continue; }
const enumMatch = tok.match(/^\$enum=\{([^}]+)\}$/);
if (enumMatch) {
rule.enum = enumMatch[1].split(/[;]/).map(s => s.trim()).filter(Boolean);
continue;
}
}
return { path, rule };
}
function parseDataLines(lines) {
const results = [];
let pendingPath = null;
let pendingLines = [];
const flushPending = () => {
if (!pendingPath) return;
// 没有任何缩进行:视为 set 空字符串
if (!pendingLines.length) {
results.push({ path: pendingPath, op: 'set', value: '' });
pendingPath = null;
pendingLines = [];
return;
}
try {
// 去除公共缩进
const nonEmpty = pendingLines.filter(l => l.trim());
const minIndent = nonEmpty.length
? Math.min(...nonEmpty.map(l => l.search(/\S/)))
@@ -121,25 +189,22 @@ function unescapeString(s) {
.replace(/\\\\/g, '\\');
}
/**
* 单行内联值解析
*/
export function parseInlineValue(raw) {
const t = String(raw ?? '').trim();
if (t === 'null') return { op: 'del' };
// (负数) 用于强制 set -5而不是 inc -5
// (负数) 强制 set
const parenNum = t.match(/^\((-?\d+(?:\.\d+)?)\)$/);
if (parenNum) return { op: 'set', value: Number(parenNum[1]) };
// +10 / -20
// +N / -N
if (/^\+\d/.test(t) || /^-\d/.test(t)) {
const n = Number(t);
if (Number.isFinite(n)) return { op: 'inc', delta: n };
}
// +"str" / +'str'
// +"str"
const pushD = t.match(/^\+"((?:[^"\\]|\\.)*)"\s*$/);
if (pushD) return { op: 'push', value: unescapeString(pushD[1]) };
const pushS = t.match(/^\+'((?:[^'\\]|\\.)*)'\s*$/);
@@ -150,13 +215,11 @@ export function parseInlineValue(raw) {
try {
const arr = JSON.parse(t.slice(1));
if (Array.isArray(arr)) return { op: 'push', value: arr };
return { op: 'set', value: t, warning: '+[] 不是数组,作为字符串' };
} catch {
return { op: 'set', value: t, warning: '+[] JSON 解析失败,作为字符串' };
}
} catch {}
return { op: 'set', value: t, warning: '+[] 解析失败' };
}
// -"str" / -'str'
// -"str"
const popD = t.match(/^-"((?:[^"\\]|\\.)*)"\s*$/);
if (popD) return { op: 'pop', value: unescapeString(popD[1]) };
const popS = t.match(/^-'((?:[^'\\]|\\.)*)'\s*$/);
@@ -167,13 +230,11 @@ export function parseInlineValue(raw) {
try {
const arr = JSON.parse(t.slice(1));
if (Array.isArray(arr)) return { op: 'pop', value: arr };
return { op: 'set', value: t, warning: '-[] 不是数组,作为字符串' };
} catch {
return { op: 'set', value: t, warning: '-[] JSON 解析失败,作为字符串' };
}
} catch {}
return { op: 'set', value: t, warning: '-[] 解析失败' };
}
// 裸数字 set
// 裸数字
if (/^-?\d+(?:\.\d+)?$/.test(t)) return { op: 'set', value: Number(t) };
// "str" / 'str'
@@ -185,12 +246,12 @@ export function parseInlineValue(raw) {
if (t === 'true') return { op: 'set', value: true };
if (t === 'false') return { op: 'set', value: false };
// JSON set
// JSON array/object
if (t.startsWith('{') || t.startsWith('[')) {
try { return { op: 'set', value: JSON.parse(t) }; }
catch { return { op: 'set', value: t, warning: 'JSON 解析失败,作为字符串' }; }
catch { return { op: 'set', value: t, warning: 'JSON 解析失败' }; }
}
// 兜底 set 原文本
// 兜底
return { op: 'set', value: t };
}

View File

@@ -5,7 +5,6 @@ export function generateSemantic(path, op, oldValue, newValue, delta, operandVal
if (v === undefined) return '空';
if (v === null) return 'null';
try {
if (typeof v === 'string') return JSON.stringify(v);
return JSON.stringify(v);
} catch {
return String(v);