Compare commits
2 Commits
df9698b67b
...
284251ddf2
| Author | SHA1 | Date |
|---|---|---|
|
|
284251ddf2 | |
|
|
8d778f9867 |
|
|
@ -8,14 +8,18 @@
|
||||||
/**
|
/**
|
||||||
* 解析命令行输入字符串为 Command 对象
|
* 解析命令行输入字符串为 Command 对象
|
||||||
* 支持格式:commandName [params...] [--flags...] [-o value...]
|
* 支持格式:commandName [params...] [--flags...] [-o value...]
|
||||||
*
|
* 支持引号:单引号 (') 和双引号 (") 可以包裹包含空格的参数
|
||||||
|
* 支持转义:使用反斜杠 (\) 转义引号或反斜杠本身
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* parseCommand("move meeple1 region1 --force -x 10")
|
* parseCommand("move meeple1 region1 --force -x 10")
|
||||||
* // returns { name: "move", params: ["meeple1", "region1"], flags: { force: true }, options: { x: "10" } }
|
* // returns { name: "move", params: ["meeple1", "region1"], flags: { force: true }, options: { x: "10" } }
|
||||||
|
* parseCommand('place tile "large castle" --x 5')
|
||||||
|
* // returns { name: "place", params: ["tile", "large castle"], flags: {}, options: { x: "5" } }
|
||||||
*/
|
*/
|
||||||
export function parseCommand(input: string): Command {
|
export function parseCommand(input: string): Command {
|
||||||
const tokens = input.trim().split(/\s+/).filter(Boolean);
|
const tokens = tokenize(input);
|
||||||
|
|
||||||
if (tokens.length === 0) {
|
if (tokens.length === 0) {
|
||||||
return { name: '', flags: {}, options: {}, params: [] };
|
return { name: '', flags: {}, options: {}, params: [] };
|
||||||
}
|
}
|
||||||
|
|
@ -33,7 +37,7 @@ export function parseCommand(input: string): Command {
|
||||||
// 长格式标志或选项:--flag 或 --option value
|
// 长格式标志或选项:--flag 或 --option value
|
||||||
const key = token.slice(2);
|
const key = token.slice(2);
|
||||||
const nextToken = tokens[i + 1];
|
const nextToken = tokens[i + 1];
|
||||||
|
|
||||||
// 如果下一个 token 存在且不以 - 开头(或者是负数),则是选项值
|
// 如果下一个 token 存在且不以 - 开头(或者是负数),则是选项值
|
||||||
if (nextToken && (!nextToken.startsWith('-') || /^-\d+$/.test(nextToken))) {
|
if (nextToken && (!nextToken.startsWith('-') || /^-\d+$/.test(nextToken))) {
|
||||||
options[key] = nextToken;
|
options[key] = nextToken;
|
||||||
|
|
@ -47,7 +51,7 @@ export function parseCommand(input: string): Command {
|
||||||
// 短格式标志或选项:-f 或 -o value(但不匹配负数)
|
// 短格式标志或选项:-f 或 -o value(但不匹配负数)
|
||||||
const key = token.slice(1);
|
const key = token.slice(1);
|
||||||
const nextToken = tokens[i + 1];
|
const nextToken = tokens[i + 1];
|
||||||
|
|
||||||
// 如果下一个 token 存在且不以 - 开头(或者是负数),则是选项值
|
// 如果下一个 token 存在且不以 - 开头(或者是负数),则是选项值
|
||||||
if (nextToken && (!nextToken.startsWith('-') || /^-\d+$/.test(nextToken))) {
|
if (nextToken && (!nextToken.startsWith('-') || /^-\d+$/.test(nextToken))) {
|
||||||
options[key] = nextToken;
|
options[key] = nextToken;
|
||||||
|
|
@ -65,4 +69,57 @@ export function parseCommand(input: string): Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
return { name, flags, options, params };
|
return { name, flags, options, params };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将输入字符串分解为 tokens,支持引号和转义
|
||||||
|
*/
|
||||||
|
function tokenize(input: string): string[] {
|
||||||
|
const tokens: string[] = [];
|
||||||
|
let current = '';
|
||||||
|
let inQuote: string | null = null; // ' 或 " 或 null
|
||||||
|
let escaped = false;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while (i < input.length) {
|
||||||
|
const char = input[i];
|
||||||
|
|
||||||
|
if (escaped) {
|
||||||
|
// 转义字符:直接添加到当前 token
|
||||||
|
current += char;
|
||||||
|
escaped = false;
|
||||||
|
} else if (char === '\\') {
|
||||||
|
// 开始转义
|
||||||
|
escaped = true;
|
||||||
|
} else if (inQuote) {
|
||||||
|
// 在引号内
|
||||||
|
if (char === inQuote) {
|
||||||
|
// 结束引号
|
||||||
|
inQuote = null;
|
||||||
|
} else {
|
||||||
|
current += char;
|
||||||
|
}
|
||||||
|
} else if (char === '"' || char === "'") {
|
||||||
|
// 开始引号
|
||||||
|
inQuote = char;
|
||||||
|
} else if (/\s/.test(char)) {
|
||||||
|
// 空白字符
|
||||||
|
if (current.length > 0) {
|
||||||
|
tokens.push(current);
|
||||||
|
current = '';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 普通字符
|
||||||
|
current += char;
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理未闭合的引号
|
||||||
|
if (current.length > 0) {
|
||||||
|
tokens.push(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
export interface RNG {
|
export interface RNG {
|
||||||
/** 设置随机数种子 */
|
/** 设置随机数种子 */
|
||||||
(seed: number): void;
|
setSeed(seed: number): void;
|
||||||
|
|
||||||
|
getSeed(): number;
|
||||||
|
|
||||||
/** 获取一个 [0,1) 随机数 */
|
/** 获取一个 [0,1) 随机数 */
|
||||||
next(max?: number): number;
|
next(max?: number): number;
|
||||||
|
|
@ -14,33 +16,7 @@
|
||||||
* 这是一个快速、高质量的 32 位 PRNG
|
* 这是一个快速、高质量的 32 位 PRNG
|
||||||
*/
|
*/
|
||||||
export function createRNG(seed?: number): RNG {
|
export function createRNG(seed?: number): RNG {
|
||||||
let currentSeed: number = seed ?? 1;
|
return new Mulberry32RNG(seed);
|
||||||
|
|
||||||
function rng(seed: number): void {
|
|
||||||
currentSeed = seed;
|
|
||||||
}
|
|
||||||
|
|
||||||
rng.next = function(max?: number): number {
|
|
||||||
let t = (currentSeed += 0x6d2b79f5);
|
|
||||||
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
||||||
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
||||||
const result = ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
||||||
return max !== undefined ? result * max : result;
|
|
||||||
};
|
|
||||||
|
|
||||||
rng.nextInt = function(max: number): number {
|
|
||||||
return Math.floor(rng.next(max));
|
|
||||||
};
|
|
||||||
|
|
||||||
(rng as any).setSeed = function(seed: number): void {
|
|
||||||
currentSeed = seed;
|
|
||||||
};
|
|
||||||
|
|
||||||
(rng as any).getSeed = function(): number {
|
|
||||||
return currentSeed;
|
|
||||||
};
|
|
||||||
|
|
||||||
return rng;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Mulberry32RNG 类实现(用于类型兼容) */
|
/** Mulberry32RNG 类实现(用于类型兼容) */
|
||||||
|
|
|
||||||
|
|
@ -111,4 +111,64 @@ describe('parseCommand', () => {
|
||||||
params: []
|
params: []
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should parse quoted string with double quotes', () => {
|
||||||
|
const result = parseCommand('place tile "large castle" --x 5');
|
||||||
|
expect(result).toEqual({
|
||||||
|
name: 'place',
|
||||||
|
flags: {},
|
||||||
|
options: { x: '5' },
|
||||||
|
params: ['tile', 'large castle']
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse quoted string with single quotes', () => {
|
||||||
|
const result = parseCommand("place tile 'large castle' --x 5");
|
||||||
|
expect(result).toEqual({
|
||||||
|
name: 'place',
|
||||||
|
flags: {},
|
||||||
|
options: { x: '5' },
|
||||||
|
params: ['tile', 'large castle']
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle escaped quotes', () => {
|
||||||
|
const result = parseCommand('say "hello \\"world\\""');
|
||||||
|
expect(result).toEqual({
|
||||||
|
name: 'say',
|
||||||
|
flags: {},
|
||||||
|
options: {},
|
||||||
|
params: ['hello "world"']
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle escaped backslash', () => {
|
||||||
|
const result = parseCommand('set path "C:\\\\Users"');
|
||||||
|
expect(result).toEqual({
|
||||||
|
name: 'set',
|
||||||
|
flags: {},
|
||||||
|
options: {},
|
||||||
|
params: ['path', 'C:\\Users']
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle mixed quotes', () => {
|
||||||
|
const result = parseCommand('cmd "hello world" \'foo bar\' --flag');
|
||||||
|
expect(result).toEqual({
|
||||||
|
name: 'cmd',
|
||||||
|
flags: { flag: true },
|
||||||
|
options: {},
|
||||||
|
params: ['hello world', 'foo bar']
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle quote in middle of argument', () => {
|
||||||
|
const result = parseCommand('cmd "hello\'s world"');
|
||||||
|
expect(result).toEqual({
|
||||||
|
name: 'cmd',
|
||||||
|
flags: {},
|
||||||
|
options: {},
|
||||||
|
params: ["hello's world"]
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -70,12 +70,6 @@ describe('createRNG', () => {
|
||||||
expect(firstSequence).toEqual(secondSequence);
|
expect(firstSequence).toEqual(secondSequence);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work as callable function', () => {
|
|
||||||
const rng = createRNG(42);
|
|
||||||
rng(123);
|
|
||||||
expect(rng.getSeed()).toBe(123);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should generate uniformly distributed integers', () => {
|
it('should generate uniformly distributed integers', () => {
|
||||||
const rng = createRNG(42);
|
const rng = createRNG(42);
|
||||||
const buckets = new Array(10).fill(0);
|
const buckets = new Array(10).fill(0);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue