import jsyaml from '../../../libs/js-yaml.mjs'; const STATE_TAG_RE = /<\s*state\b[^>]*>([\s\S]*?)<\s*\/\s*state\s*>/gi; export function extractStateBlocks(text) { const s = String(text ?? ''); if (!s || s.toLowerCase().indexOf(' 块 * 返回: { rules: [{path, rule}], ops: [{path, op, value, ...}] } */ export function parseStateBlock(content) { 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; 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/))) : 0; const yamlText = pendingLines .map(l => (l.trim() ? l.slice(minIndent) : '')) .join('\n'); const obj = jsyaml.load(yamlText); results.push({ path: pendingPath, op: 'set', value: obj }); } catch (e) { results.push({ path: pendingPath, op: 'set', value: null, warning: `YAML 解析失败: ${e.message}` }); } finally { pendingPath = null; pendingLines = []; } }; for (const raw of lines) { const trimmed = raw.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const indent = raw.search(/\S/); if (indent === 0) { flushPending(); const colonIdx = findTopLevelColon(trimmed); if (colonIdx === -1) continue; const path = trimmed.slice(0, colonIdx).trim(); const rhs = trimmed.slice(colonIdx + 1).trim(); if (!path) continue; if (!rhs) { pendingPath = path; pendingLines = []; } else { results.push({ path, ...parseInlineValue(rhs) }); } } else if (pendingPath) { pendingLines.push(raw); } } flushPending(); return results; } function findTopLevelColon(line) { let inQuote = false; let q = ''; let esc = false; for (let i = 0; i < line.length; i++) { const ch = line[i]; if (esc) { esc = false; continue; } if (ch === '\\') { esc = true; continue; } if (!inQuote && (ch === '"' || ch === "'")) { inQuote = true; q = ch; continue; } if (inQuote && ch === q) { inQuote = false; q = ''; continue; } if (!inQuote && ch === ':') return i; } return -1; } function unescapeString(s) { return String(s ?? '') .replace(/\\n/g, '\n') .replace(/\\t/g, '\t') .replace(/\\r/g, '\r') .replace(/\\"/g, '"') .replace(/\\'/g, "'") .replace(/\\\\/g, '\\'); } export function parseInlineValue(raw) { const t = String(raw ?? '').trim(); if (t === 'null') return { op: 'del' }; // (负数) 强制 set const parenNum = t.match(/^\((-?\d+(?:\.\d+)?)\)$/); if (parenNum) return { op: 'set', value: Number(parenNum[1]) }; // +N / -N if (/^\+\d/.test(t) || /^-\d/.test(t)) { const n = Number(t); if (Number.isFinite(n)) return { op: 'inc', delta: n }; } // +"str" const pushD = t.match(/^\+"((?:[^"\\]|\\.)*)"\s*$/); if (pushD) return { op: 'push', value: unescapeString(pushD[1]) }; const pushS = t.match(/^\+'((?:[^'\\]|\\.)*)'\s*$/); if (pushS) return { op: 'push', value: unescapeString(pushS[1]) }; // +[...] if (t.startsWith('+[')) { try { const arr = JSON.parse(t.slice(1)); if (Array.isArray(arr)) return { op: 'push', value: arr }; } catch {} return { op: 'set', value: t, warning: '+[] 解析失败' }; } // -"str" const popD = t.match(/^-"((?:[^"\\]|\\.)*)"\s*$/); if (popD) return { op: 'pop', value: unescapeString(popD[1]) }; const popS = t.match(/^-'((?:[^'\\]|\\.)*)'\s*$/); if (popS) return { op: 'pop', value: unescapeString(popS[1]) }; // -[...] if (t.startsWith('-[')) { try { const arr = JSON.parse(t.slice(1)); if (Array.isArray(arr)) return { op: 'pop', value: arr }; } catch {} return { op: 'set', value: t, warning: '-[] 解析失败' }; } // 裸数字 if (/^-?\d+(?:\.\d+)?$/.test(t)) return { op: 'set', value: Number(t) }; // "str" / 'str' const strD = t.match(/^"((?:[^"\\]|\\.)*)"\s*$/); if (strD) return { op: 'set', value: unescapeString(strD[1]) }; const strS = t.match(/^'((?:[^'\\]|\\.)*)'\s*$/); if (strS) return { op: 'set', value: unescapeString(strS[1]) }; if (t === 'true') return { op: 'set', value: true }; if (t === 'false') return { op: 'set', value: false }; // JSON array/object if (t.startsWith('{') || t.startsWith('[')) { try { return { op: 'set', value: JSON.parse(t) }; } catch { return { op: 'set', value: t, warning: 'JSON 解析失败' }; } } // 兜底 return { op: 'set', value: t }; }