import type { Token, DicePool } from "./types"; /** * 词法分析器 - 将输入字符串转换为令牌流 */ export function tokenize(input: string): Token[] { const tokens: Token[] = []; let i = 0; const str = input.replace(/\s+/g, ""); // 移除所有空格 while (i < str.length) { const char = str[i]; // 修饰符 kh/kl/dh/dl - 必须在检查 'd' 骰子之前检查 if (char === "k" || char === "d") { const rest = str.slice(i).toLowerCase(); if (rest.startsWith("kh")) { i += 2; let countStr = ""; while (i < str.length && /\d/.test(str[i])) { countStr += str[i]; i++; } tokens.push({ type: "modifier", modType: "kh", count: parseInt(countStr) || 1 }); continue; } else if (rest.startsWith("kl")) { i += 2; let countStr = ""; while (i < str.length && /\d/.test(str[i])) { countStr += str[i]; i++; } tokens.push({ type: "modifier", modType: "kl", count: parseInt(countStr) || 1 }); continue; } else if (rest.startsWith("dh")) { i += 2; let countStr = ""; while (i < str.length && /\d/.test(str[i])) { countStr += str[i]; i++; } tokens.push({ type: "modifier", modType: "dh", count: parseInt(countStr) || 1 }); continue; } else if (rest.startsWith("dl")) { i += 2; let countStr = ""; while (i < str.length && /\d/.test(str[i])) { countStr += str[i]; i++; } tokens.push({ type: "modifier", modType: "dl", count: parseInt(countStr) || 1 }); continue; } } // 数字字面量 if (/\d/.test(char)) { let numStr = ""; while (i < str.length && /\d/.test(str[i])) { numStr += str[i]; i++; } const num = parseInt(numStr); // 检查是否是 NdX 格式 if (str[i] === "d" || str[i] === "D") { i++; // 跳过 'd' let sidesStr = ""; while (i < str.length && /\d/.test(str[i])) { sidesStr += str[i]; i++; } const sides = parseInt(sidesStr); tokens.push({ type: "dice", count: num, sides }); } else { tokens.push({ type: "number", value: num }); } continue; } // 单独的 dX 格式(等同于 1dX) if (char === "d" || char === "D") { i++; let sidesStr = ""; while (i < str.length && /\d/.test(str[i])) { sidesStr += str[i]; i++; } if (sidesStr.length > 0) { const sides = parseInt(sidesStr); tokens.push({ type: "dice", count: 1, sides }); } continue; } // 骰池开始 if (char === "{") { tokens.push({ type: "pool_start" }); i++; continue; } // 骰池结束 if (char === "}") { tokens.push({ type: "pool_end" }); i++; continue; } // 逗号 if (char === ",") { tokens.push({ type: "comma" }); i++; continue; } // 加号 if (char === "+") { tokens.push({ type: "plus" }); i++; continue; } // 减号 if (char === "-") { tokens.push({ type: "minus" }); i++; continue; } // 未知字符,跳过 i++; } return tokens; } /** * 语法分析器 - 将令牌流转换为骰池列表 */ export function parse(tokens: Token[]): DicePool[] { const pools: DicePool[] = []; let i = 0; let currentSign = 1; // 当前符号,1 为正,-1 为负 while (i < tokens.length) { const token = tokens[i]; // 处理运算符 if (token.type === "plus") { currentSign = 1; i++; continue; } if (token.type === "minus") { currentSign = -1; i++; continue; } // 解析骰池 const pool = parsePool(tokens, i, currentSign); if (pool) { pools.push(pool.pool); i = pool.nextIndex; currentSign = 1; // 重置符号 } else { i++; } } return pools; } /** * 解析单个骰池 */ function parsePool( tokens: Token[], startIndex: number, sign: number ): { pool: DicePool; nextIndex: number } | null { const dice: DicePool["dice"] = []; let i = startIndex; let modifier: DicePool["modifier"] = undefined; const token = tokens[i]; if (!token) return null; // 骰池字面量 {d4, d6, ...} if (token.type === "pool_start") { i++; // 跳过 { while (i < tokens.length && tokens[i].type !== "pool_end") { if (tokens[i].type === "comma") { i++; continue; } const token = tokens[i]; if (token.type === "dice") { for (let j = 0; j < token.count; j++) { dice.push({ sides: token.sides, value: 0, isNegative: sign < 0, }); } i++; } else if (token.type === "number") { // 数字作为 0 面骰子(固定值) dice.push({ sides: 0, value: token.value, isNegative: sign < 0, }); i++; } else { i++; } } i++; // 跳过 } } // 单个骰子或数字 else if (token.type === "dice") { for (let j = 0; j < token.count; j++) { dice.push({ sides: token.sides, value: 0, isNegative: sign < 0, }); } i++; } else if (token.type === "number") { dice.push({ sides: 0, value: token.value, isNegative: sign < 0, }); i++; } else { return null; } // 检查修饰符 if (i < tokens.length && tokens[i].type === "modifier") { const mod = tokens[i] as Extract; modifier = { type: mod.modType, count: mod.count }; i++; } return { pool: { dice, modifier, isNegative: sign < 0 }, nextIndex: i, }; }