Compare commits

..

No commits in common. "284251ddf27d09b4f4e238abbff1e40b1f8096fd" and "df9698b67bca65d9b18de7ed0235370041b5aabb" have entirely different histories.

4 changed files with 40 additions and 127 deletions

View File

@ -8,18 +8,14 @@
/**
* Command
* commandName [params...] [--flags...] [-o value...]
* (') (")
* 使 (\)
*
*
* @example
* parseCommand("move meeple1 region1 --force -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 {
const tokens = tokenize(input);
const tokens = input.trim().split(/\s+/).filter(Boolean);
if (tokens.length === 0) {
return { name: '', flags: {}, options: {}, params: [] };
}
@ -37,7 +33,7 @@ export function parseCommand(input: string): Command {
// 长格式标志或选项:--flag 或 --option value
const key = token.slice(2);
const nextToken = tokens[i + 1];
// 如果下一个 token 存在且不以 - 开头(或者是负数),则是选项值
if (nextToken && (!nextToken.startsWith('-') || /^-\d+$/.test(nextToken))) {
options[key] = nextToken;
@ -51,7 +47,7 @@ export function parseCommand(input: string): Command {
// 短格式标志或选项:-f 或 -o value但不匹配负数
const key = token.slice(1);
const nextToken = tokens[i + 1];
// 如果下一个 token 存在且不以 - 开头(或者是负数),则是选项值
if (nextToken && (!nextToken.startsWith('-') || /^-\d+$/.test(nextToken))) {
options[key] = nextToken;
@ -69,57 +65,4 @@ export function parseCommand(input: string): Command {
}
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;
}
}

View File

@ -1,8 +1,6 @@
export interface RNG {
/** 设置随机数种子 */
setSeed(seed: number): void;
getSeed(): number;
(seed: number): void;
/** 获取一个 [0,1) 随机数 */
next(max?: number): number;
@ -16,7 +14,33 @@
* 32 PRNG
*/
export function createRNG(seed?: number): RNG {
return new Mulberry32RNG(seed);
let currentSeed: number = seed ?? 1;
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 类实现(用于类型兼容) */

View File

@ -111,64 +111,4 @@ describe('parseCommand', () => {
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"]
});
});
});

View File

@ -70,6 +70,12 @@ describe('createRNG', () => {
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', () => {
const rng = createRNG(42);
const buckets = new Array(10).fill(0);