Compare commits

..

7 Commits

Author SHA1 Message Date
9aad52347b refactor(tracers): 统一命令字段名并重构数组追踪器创建逻辑
- 将所有命令的 `params` 字段重命名为 `payload`,以保持命名一致性
- 将 `ArrayTracerCreateOptions` 从联合类型简化为单一接口,移除 `array` 参数,新增 `initial` 和 `walker` 参数
- 引入 `buildInitial` 函数集中处理初始数组的构建逻辑
- 使用 `metadata` 对象管理数组长度状态,替代独立的 `arrayLength` 变量
- 更新错误消息格式,移除方括号前缀
- 调整泛型参数,使 `createArrayTracer` 更通用
2026-03-04 02:34:12 +08:00
2dfc4ced67 docs: 优化开发规则与项目介绍文档的表述
- 移除关于禁止修改源代码的临时限制说明,该限制已不再适用
- 统一并优化多处中文标点与空格的格式,使文档更整洁
- 调整项目介绍中关于协议设计的表述,使其更清晰准确
- 合并`tracer-type.md`中的分隔线,使列表结构更紧凑
2026-03-02 21:06:36 +08:00
19d4b97669 fix(tracer): 修复异步scope中异常处理后的状态设置问题
发生ScopeError时不执行flush,否则即便抛出异常也会执行flush
2026-02-24 20:33:59 +08:00
8a2399e890 docs(tracer-context): 添加详细的中文注释说明scope的设计原理和实现细节。 2026-02-23 13:03:22 +08:00
9f08c34225 refactor(tracer-context): 引入ScopeError类并优化错误处理逻辑
- 新增ScopeError自定义错误类,用于区分范围相关错误
- 将error字段重命名为exception以更准确描述其用途
- 优化flush方法,仅在有必要时输出指令序列
- 简化错误处理逻辑,移除冗余的console.error调用
- 确保ScopeError能正确向上抛出而不被捕获
2026-02-23 03:20:13 +08:00
134d4e2b19 refactor(tracer-context): 改进 scope 状态管理以支持嵌套检测
- 引入 SCOPE_STATE 枚举明确区分初始、执行中和已完成状态
- 将 scoping 布尔标志替换为 state 枚举,支持检测嵌套调用
- 分离同步和异步错误处理流程,确保状态正确更新
- 修复 flush 方法中 commands 和 error 的清理逻辑
2026-02-23 02:05:58 +08:00
8894458f4c feat(tracer-context): 添加scope方法以支持追踪异步错误
新增scope方法用于包裹异步或同步代码块,自动捕获错误并记录到上下文中。同时将dump方法重命名为flush以更准确描述其行为(清空并输出指令序列)。重构内部数据结构,将commands和error统一管理在scopeResult对象中。
2026-02-22 22:37:06 +08:00
6 changed files with 153 additions and 72 deletions

View File

@@ -1,7 +1,6 @@
# 开发规则 # 开发规则
- **严禁主动修改任何项目源代码文件(如 .ts, .js, .c, .h 等)。当前工作仅限于文档编写(.md和方案讨论。所有代码示例仅能在对话中展示不得写入文件。** - 在设计协议和API时我们需要考虑API的移植性尤其需要特别关注C语言因为C语言的语法表达力不算强。当然各类主流的编程语言都需要纳入考虑范围包括但不限于
- 在考虑API的移植性时需要特别考虑C语言当然各类主流的编程语言要需要纳入考虑范围包括但不限于
- C++ - C++
- Java - Java
- C# - C#

View File

@@ -6,7 +6,7 @@
基于上述设想,我肯定无法接受从编译技术的角度去适配每一种编程语言(例如代码插桩甚至改造编译器),因为这会导致平台的开发变得非常复杂,维护成本也会非常高。因此我的想法是设计一套语言无关的协议,用来描述各种数据结构的变化(跟踪器),因此这套协议会定义需要支持的数据结构类型,以及每一种数据结构类型需要支持的操作指令,并且我倾向于将指令设计得更加“底层”一些,类似于“原语”的概念。 基于上述设想,我肯定无法接受从编译技术的角度去适配每一种编程语言(例如代码插桩甚至改造编译器),因为这会导致平台的开发变得非常复杂,维护成本也会非常高。因此我的想法是设计一套语言无关的协议,用来描述各种数据结构的变化(跟踪器),因此这套协议会定义需要支持的数据结构类型,以及每一种数据结构类型需要支持的操作指令,并且我倾向于将指令设计得更加“底层”一些,类似于“原语”的概念。
> 例如针对顺序表结构,我可能会定义 pick(获取元素)、drop(取消获取)、patch(修改元素) 指令来替代 swap(交换元素) 操作,因为交换两个元素的本质就是 获取元素 + 修改元素,而我在其中添加 drop 指令的目的则是使可视化过程更加具体,例如 pick 指令会将其选定的元素高亮,那么 drop 指令就可以取消该元素的高亮行为。 > 例如针对顺序表结构我可能会定义pick(获取元素)、drop(取消获取)、patch(修改元素)指令来替代swap(交换元素)操作,因为交换两个元素的本质就是获取元素+修改元素而我在其中添加rop指令的目的则是使可视化过程更加具体例如pick指令会将其选定的元素高亮那么drop指令就可以取消该元素的高亮行为。
我们会先选取一种编程语言(例如TypeScript)来实现这套协议,在实现的过程中,需要考虑到未来将添加其他编程语言的支持,因此需要考虑到不同编程语言之间的差异,例如语法/类型系统差异,进而反过来优化这套协议,以使其能够更好地支持不同的编程语言,这是一个双向的过程。 我们会先选取一种编程语言(例如TypeScript)来实现这套协议,在实现的过程中,需要考虑到未来将添加其他编程语言的支持,因此需要考虑到不同编程语言之间的差异,例如语法/类型系统差异,进而反过来优化这套协议,以使其能够更好地支持不同的编程语言,这是一个双向的过程。

View File

@@ -12,8 +12,5 @@ Tracer 代表一个数据结构变化的跟踪器,用户可以通过调用 Tra
- LinkTracer: 链表 - LinkTracer: 链表
- TreeTracer: 树 - TreeTracer: 树
- GraphTracer: 图 - GraphTracer: 图
---
- LogTracer: 日志 - LogTracer: 日志
- ControlTracer: 控制 - ControlTracer: 控制

View File

@@ -1,35 +1,113 @@
import type { TracerCommand } from '../types'; import type { TracerCommand } from '../types';
const createTracerContext = () => { class ScopeError extends Error {
const commands: TracerCommand[] = []; constructor(message?: string) {
super(message);
this.name = 'ScopeError';
}
}
// if (typeof process !== 'undefined' && typeof process.on === 'function') { const SCOPE_STATE = {
// process.on('exit', () => { INITIAL: 'initial',
// if (commands.length > 0) { SCOPING: 'scoping',
// console.log(commands); SCOPED: 'scoped',
// } } as const;
// });
// } type ScopeState = (typeof SCOPE_STATE)[keyof typeof SCOPE_STATE];
type Error = {
stack?: string;
};
type ScopeResult = {
// 记录指令序列
commands: TracerCommand[];
// 记录异常信息
exception: Error;
// 检查scope的执行状态
state: ScopeState;
};
const createTracerContext = () => {
const scopeResult: ScopeResult = {
commands: [],
exception: {},
state: SCOPE_STATE.INITIAL,
};
const command = (command: TracerCommand) => {
scopeResult.commands.push(command);
};
// TODO: 输出指令序列和错误信息
const flush = () => {
const { commands, exception } = scopeResult;
if (commands.length > 0 || !!exception.stack) {
console.log(JSON.stringify({ commands, exception }, null, 2));
}
scopeResult.commands = [];
scopeResult.exception = {};
};
const getTracerContext = () => { const getTracerContext = () => {
const command = (command: TracerCommand) => {
commands.push(command);
};
// TODO: 输出指令序列
const dump = () => {
return commands;
};
return { return {
command, command,
dump, flush,
}; };
}; };
// 由于JavaScript/TypeScript在语言层面缺乏统一的“程序退出钩子”机制
// 所以我们无法在算法执行结束后自动地输出指令序列和异常信息,
// 尽管像Node.js这样的运行时提供了process.on('exit')但是我们希望保持SDK的环境无关性
// 所以为了不依赖于特定运行时的API我们设计了scope方法来创建一个显式的生命周期
// 算法逻辑将会被一个回调函数包裹作为routine参数
// 在routine执行完毕后scope会向外输出指令序列和异常信息。
const scope = (routine: () => void | Promise<void>) => {
if (scopeResult.state === SCOPE_STATE.SCOPED) {
throw new ScopeError('ScopeError: Detect multiple scope.');
}
if (scopeResult.state === SCOPE_STATE.SCOPING) {
throw new ScopeError('ScopeError: Detect nested scope.');
}
scopeResult.state = SCOPE_STATE.SCOPING;
// 如果routine是同步函数那可能会抛出同步错误所以需要用try-catch包裹
try {
const result = routine();
// 如果routine是异步函数需要用catch捕获异步错误
if (result instanceof Promise) {
result
.then(() => {
scopeResult.state = SCOPE_STATE.SCOPED;
flush();
})
.catch((error) => {
// 如果是ScopeError需要抛出否则会在嵌套调用scope时被外层scope捕获
if (error instanceof ScopeError) {
throw error;
}
scopeResult.exception.stack = (error as Error).stack;
scopeResult.state = SCOPE_STATE.SCOPED;
flush();
});
} else {
// 如果routine是同步函数执行完成后需要设置scope状态
scopeResult.state = SCOPE_STATE.SCOPED;
flush();
}
} catch (error) {
if (error instanceof ScopeError) {
throw error;
}
scopeResult.exception.stack = (error as Error).stack;
scopeResult.state = SCOPE_STATE.SCOPED;
flush();
}
};
return { return {
getTracerContext, getTracerContext,
scope,
}; };
}; };
export const { getTracerContext } = createTracerContext(); export const { getTracerContext, scope } = createTracerContext();

View File

@@ -1,44 +1,51 @@
import { getTracerContext } from '../context'; import { getTracerContext } from '../context';
import type { JsonValue } from '../types'; import type { JsonValue } from '../types';
interface BaseArrayTracerCreateOptions { interface ArrayTracerCreateOptions<T extends JsonValue> {
description?: string; description?: string;
initial?: T[];
walker?: (builder: { add: (item: T) => void }) => void;
} }
interface ArrayTracerCreateOptionsFromArray< type ArrayTracerMetadata = {
T extends JsonValue[], // initial: JsonValue[];
> extends BaseArrayTracerCreateOptions { length: number;
array: T; };
walker?: never;
}
interface ArrayTracerCreateOptionsFromWalker< export const createArrayTracer = <T extends JsonValue = JsonValue>(
T extends JsonValue[],
> extends BaseArrayTracerCreateOptions {
array?: never;
walker: (commit: (item: T[number]) => void) => void;
}
type ArrayTracerCreateOptions<T extends JsonValue[]> =
| ArrayTracerCreateOptionsFromArray<T>
| ArrayTracerCreateOptionsFromWalker<T>;
export const createArrayTracer = <T extends JsonValue[]>(
options: ArrayTracerCreateOptions<T>, options: ArrayTracerCreateOptions<T>,
) => { ) => {
const { description, array, walker } = options; const { description, initial, walker } = options;
const tracer = crypto.randomUUID(); const tracer = crypto.randomUUID();
// 优化:仅维护数组长度作为影子状态,这在 C++/Java 等强类型语言中也极易实现(仅需一个 int 变量) const metadata: ArrayTracerMetadata = {
// 这种“最小必要状态”策略既能实现越界校验,又避免了在强类型语言中处理泛型存储的复杂性,且内存开销极低。 length: 0,
let arrayLength = array ? array.length : 0; };
const buildInitial = () => {
if (!!initial) {
return [...initial];
}
if (!!walker) {
const initial: T[] = [];
walker({
add: (item) => {
initial.push(item);
},
});
return initial;
}
return [];
};
// console.log(_initial);
const { command } = getTracerContext(); const { command } = getTracerContext();
const validateIndex = (index: number) => { const validateIndex = (index: number) => {
if (index < 0 || index >= arrayLength) { if (index < 0 || index >= metadata.length) {
throw new Error( throw new Error(
`[ArrayTracer] Index out of bounds: index ${index} is not within [0, ${arrayLength})`, `ArrayTracer: Index out of bounds: index ${index} is not within [0, ${metadata.length})`,
); );
} }
}; };
@@ -47,18 +54,18 @@ export const createArrayTracer = <T extends JsonValue[]>(
type: 'ArrayTracer', type: 'ArrayTracer',
tracer: tracer, tracer: tracer,
action: 'create', action: 'create',
params: { payload: {
description: description ?? 'ArrayTracer', description: description ?? 'ArrayTracer',
array: array ?? [], initial: buildInitial(),
}, },
}); });
// size 为正时,数组长度增加;为负时,数组长度减少(但不能小于 0 // size 为正时,数组长度增加;为负时,数组长度减少(但不能小于 0
const scale = (size: number) => { const scale = (size: number) => {
arrayLength += size; metadata.length += size;
if (arrayLength < 0) { if (metadata.length < 0) {
throw new Error( throw new Error(
`[ArrayTracer] Invalid size: ${size}, array length cannot be negative`, `ArrayTracer: Invalid size: ${size}, array length cannot be negative`,
); );
} }
@@ -66,7 +73,7 @@ export const createArrayTracer = <T extends JsonValue[]>(
type: 'ArrayTracer', type: 'ArrayTracer',
tracer: tracer, tracer: tracer,
action: 'scale', action: 'scale',
params: { payload: {
size: size, size: size,
}, },
}); });
@@ -79,7 +86,7 @@ export const createArrayTracer = <T extends JsonValue[]>(
type: 'ArrayTracer', type: 'ArrayTracer',
tracer: tracer, tracer: tracer,
action: 'pick', action: 'pick',
params: { payload: {
index: index, index: index,
}, },
}); });
@@ -92,20 +99,20 @@ export const createArrayTracer = <T extends JsonValue[]>(
type: 'ArrayTracer', type: 'ArrayTracer',
tracer: tracer, tracer: tracer,
action: 'drop', action: 'drop',
params: { payload: {
index: index, index: index,
}, },
}); });
}; };
const patch = (index: number, value: T[number]) => { const patch = (index: number, value: T) => {
validateIndex(index); validateIndex(index);
command({ command({
type: 'ArrayTracer', type: 'ArrayTracer',
tracer: tracer, tracer: tracer,
action: 'patch', action: 'patch',
params: { payload: {
index: index, index: index,
value: value, value: value,
}, },
@@ -119,7 +126,7 @@ export const createArrayTracer = <T extends JsonValue[]>(
type: 'ArrayTracer', type: 'ArrayTracer',
tracer: tracer, tracer: tracer,
action: 'unset', action: 'unset',
params: { payload: {
index: index, index: index,
}, },
}); });

View File

@@ -6,36 +6,36 @@ type BaseArrayTracerCommand = BaseTracerCommand & {
type ArrayTracerCreateCommand = BaseArrayTracerCommand & { type ArrayTracerCreateCommand = BaseArrayTracerCommand & {
action: 'create'; action: 'create';
params: { payload: {
description: string; description: string;
array: JsonValue[]; initial: JsonValue[];
}; };
}; };
type ArrayTracerScaleCommand = BaseArrayTracerCommand & { type ArrayTracerScaleCommand = BaseArrayTracerCommand & {
action: 'scale'; action: 'scale';
params: { payload: {
size: number; size: number;
}; };
}; };
type ArrayTracerPickCommand = BaseArrayTracerCommand & { type ArrayTracerPickCommand = BaseArrayTracerCommand & {
action: 'pick'; action: 'pick';
params: { payload: {
index: number; index: number;
}; };
}; };
type ArrayTracerDropCommand = BaseArrayTracerCommand & { type ArrayTracerDropCommand = BaseArrayTracerCommand & {
action: 'drop'; action: 'drop';
params: { payload: {
index: number; index: number;
}; };
}; };
type ArrayTracerPatchCommand = BaseArrayTracerCommand & { type ArrayTracerPatchCommand = BaseArrayTracerCommand & {
action: 'patch'; action: 'patch';
params: { payload: {
index: number; index: number;
value: JsonValue; value: JsonValue;
}; };
@@ -43,7 +43,7 @@ type ArrayTracerPatchCommand = BaseArrayTracerCommand & {
type ArrayTracerUnsetCommand = BaseArrayTracerCommand & { type ArrayTracerUnsetCommand = BaseArrayTracerCommand & {
action: 'unset'; action: 'unset';
params: { payload: {
index: number; index: number;
}; };
}; };