This commit is contained in:
@@ -101,16 +101,21 @@ const nextLine = computed(() => {
|
|||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
// 调试信息相关
|
// 当前步骤对象
|
||||||
|
const currentTraceEntry = computed(() => {
|
||||||
|
return debugData.value?.trace?.[currentStep.value] ?? null
|
||||||
|
})
|
||||||
|
|
||||||
|
// 调试信息相关:优先显示栈顶(高亮)帧的局部变量,没有则用全局
|
||||||
const currentVariables = computed(() => {
|
const currentVariables = computed(() => {
|
||||||
if (
|
const entry = currentTraceEntry.value
|
||||||
debugData.value &&
|
if (!entry) return {}
|
||||||
debugData.value.trace &&
|
const stack = entry.stack_to_render ?? []
|
||||||
debugData.value.trace[currentStep.value]
|
const topFrame = stack.find((f: any) => f.is_highlighted) ?? stack[stack.length - 1]
|
||||||
) {
|
if (topFrame && topFrame.encoded_locals) {
|
||||||
return debugData.value.trace[currentStep.value].globals || {}
|
return { ...(entry.globals ?? {}), ...topFrame.encoded_locals }
|
||||||
}
|
}
|
||||||
return {}
|
return entry.globals ?? {}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 格式化变量显示
|
// 格式化变量显示
|
||||||
@@ -120,30 +125,42 @@ const formattedVariables = computed(() => {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.entries(variables).map(([key, value]) => {
|
const heap: Record<string, any> =
|
||||||
// 处理特殊类型
|
debugData.value?.trace?.[currentStep.value]?.heap ?? {}
|
||||||
let displayValue = ""
|
|
||||||
let displayType = typeof value
|
|
||||||
|
|
||||||
|
return Object.entries(variables)
|
||||||
|
.filter(([, value]) => {
|
||||||
|
// 隐藏导入的模块/函数占位符
|
||||||
if (
|
if (
|
||||||
Array.isArray(value) &&
|
Array.isArray(value) &&
|
||||||
value.length === 2 &&
|
value[0] === "IMPORTED_FAUX_PRIMITIVE"
|
||||||
value[0] === "IMPORTED_FAUX_PRIMITIVE" &&
|
)
|
||||||
value[1] === "imported object"
|
return false
|
||||||
) {
|
if (Array.isArray(value) && value[0] === "FUNCTION") return false
|
||||||
displayValue = ""
|
return true
|
||||||
displayType = "function"
|
})
|
||||||
} else if (typeof value === "object" && value !== null) {
|
.map(([key, value]) => {
|
||||||
displayValue = JSON.stringify(value, null, 2)
|
const displayValue = decodeValue(value, heap)
|
||||||
} else {
|
// resolve REF before checking tag
|
||||||
displayValue = String(value)
|
const resolved =
|
||||||
|
Array.isArray(value) && value[0] === "REF"
|
||||||
|
? heap[String(value[1])]
|
||||||
|
: value
|
||||||
|
const tag = Array.isArray(resolved) ? resolved[0] : null
|
||||||
|
const typeMap: Record<string, string> = {
|
||||||
|
LIST: "list",
|
||||||
|
TUPLE: "tuple",
|
||||||
|
SET: "set",
|
||||||
|
DICT: "dict",
|
||||||
|
FUNCTION: "function",
|
||||||
|
INSTANCE: "object",
|
||||||
|
INSTANCE_PPRINT: "object",
|
||||||
|
CLASS: "class",
|
||||||
}
|
}
|
||||||
|
const displayType =
|
||||||
|
tag && typeMap[tag] ? typeMap[tag] : typeof value
|
||||||
|
|
||||||
return {
|
return { name: key, value: displayValue, type: displayType }
|
||||||
name: key,
|
|
||||||
value: displayValue,
|
|
||||||
type: displayType,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -162,7 +179,15 @@ const currentLineText = computed(() => {
|
|||||||
) {
|
) {
|
||||||
const step = debugData.value.trace[currentStep.value]
|
const step = debugData.value.trace[currentStep.value]
|
||||||
const isLastStep = currentStep.value === debugData.value.trace.length - 1
|
const isLastStep = currentStep.value === debugData.value.trace.length - 1
|
||||||
const eventText = isLastStep ? "" : getEventText(step.event)
|
// 异常/输入等待/超步数:保留事件提示,让用户能看到状态
|
||||||
|
const isStatusEvent =
|
||||||
|
step.event === "exception" ||
|
||||||
|
step.event === "uncaught_exception" ||
|
||||||
|
step.event === "raw_input" ||
|
||||||
|
step.event === "mouse_input" ||
|
||||||
|
step.event === "instruction_limit_reached"
|
||||||
|
const eventText =
|
||||||
|
isLastStep && !isStatusEvent ? "" : getEventText(step.event)
|
||||||
const stepText = isLastStep
|
const stepText = isLastStep
|
||||||
? "最后一步"
|
? "最后一步"
|
||||||
: `当前第${currentStep.value + 1}步`
|
: `当前第${currentStep.value + 1}步`
|
||||||
@@ -188,63 +213,118 @@ const nextLineText = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// ==================== 工具函数 ====================
|
// ==================== 工具函数 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 pg_encoder 编码值解析为可读字符串,heap 用于解引用 REF
|
||||||
|
*/
|
||||||
|
function decodeValue(val: any, heap: Record<string, any>, depth = 0): string {
|
||||||
|
if (depth > 8) return "..."
|
||||||
|
if (val === null || val === undefined) return "None"
|
||||||
|
if (typeof val === "boolean") return val ? "True" : "False"
|
||||||
|
if (typeof val === "string") return JSON.stringify(val)
|
||||||
|
if (!Array.isArray(val)) return String(val)
|
||||||
|
|
||||||
|
const [tag, ...rest] = val
|
||||||
|
switch (tag) {
|
||||||
|
case "REF": {
|
||||||
|
const obj = heap[String(rest[0])]
|
||||||
|
return obj ? decodeValue(obj, heap, depth + 1) : `REF(${rest[0]})`
|
||||||
|
}
|
||||||
|
case "LIST":
|
||||||
|
return "[" + rest.map((e: any) => decodeValue(e, heap, depth + 1)).join(", ") + "]"
|
||||||
|
case "TUPLE":
|
||||||
|
return rest.length === 1
|
||||||
|
? "(" + decodeValue(rest[0], heap, depth + 1) + ",)"
|
||||||
|
: "(" + rest.map((e: any) => decodeValue(e, heap, depth + 1)).join(", ") + ")"
|
||||||
|
case "SET":
|
||||||
|
return "{" + rest.map((e: any) => decodeValue(e, heap, depth + 1)).join(", ") + "}"
|
||||||
|
case "DICT":
|
||||||
|
return (
|
||||||
|
"{" +
|
||||||
|
rest
|
||||||
|
.map(([k, v]: [any, any]) => decodeValue(k, heap, depth + 1) + ": " + decodeValue(v, heap, depth + 1))
|
||||||
|
.join(", ") +
|
||||||
|
"}"
|
||||||
|
)
|
||||||
|
case "FUNCTION":
|
||||||
|
return `<function ${rest[0]}>`
|
||||||
|
case "INSTANCE":
|
||||||
|
return `<${rest[0]} instance>`
|
||||||
|
case "INSTANCE_PPRINT":
|
||||||
|
// [class_name, __str__ value, [attr, value], ...]
|
||||||
|
return `<${rest[0]}: ${rest[1]}>`
|
||||||
|
case "CLASS":
|
||||||
|
return `<class ${rest[0]}>`
|
||||||
|
case "SPECIAL_FLOAT":
|
||||||
|
return String(rest[0])
|
||||||
|
case "IMPORTED_FAUX_PRIMITIVE":
|
||||||
|
return String(rest[0])
|
||||||
|
default:
|
||||||
|
return rest.length === 1 ? String(rest[0]) : JSON.stringify(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取事件类型的中文描述
|
* 获取事件类型的中文描述
|
||||||
*/
|
*/
|
||||||
function getEventText(event: string): string {
|
function getEventText(event: string): string {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case "step_line":
|
case "step_line":
|
||||||
return "" // 普通执行不显示额外文字
|
return ""
|
||||||
case "call":
|
case "call":
|
||||||
return "(调用函数)"
|
return "(调用函数)"
|
||||||
case "return":
|
case "return":
|
||||||
return "(函数返回)"
|
return "(函数返回)"
|
||||||
case "exception":
|
case "exception":
|
||||||
return "(异常)"
|
|
||||||
case "uncaught_exception":
|
case "uncaught_exception":
|
||||||
return "(异常)"
|
return "(异常)"
|
||||||
case "raw_input":
|
case "raw_input":
|
||||||
|
case "mouse_input":
|
||||||
return "(等待输入)"
|
return "(等待输入)"
|
||||||
|
case "instruction_limit_reached":
|
||||||
|
return "(超出步数上限)"
|
||||||
default:
|
default:
|
||||||
return event || ""
|
return event || ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 输出相关
|
// 输出:stdout 和异常信息合并显示,不互相覆盖
|
||||||
const currentOutput = computed(() => {
|
const currentOutput = computed(() => {
|
||||||
|
const entry = currentTraceEntry.value
|
||||||
|
if (!entry) return output.value || ""
|
||||||
|
|
||||||
|
let outputText = (entry.stdout ?? "").trimEnd()
|
||||||
|
|
||||||
if (
|
if (
|
||||||
debugData.value &&
|
entry.event === "exception" ||
|
||||||
debugData.value.trace &&
|
entry.event === "uncaught_exception" ||
|
||||||
debugData.value.trace.length > 0
|
entry.event === "instruction_limit_reached"
|
||||||
) {
|
) {
|
||||||
let outputText = ""
|
if (entry.exception_msg) {
|
||||||
|
outputText = outputText
|
||||||
for (let i = 0; i <= currentStep.value; i++) {
|
? outputText + "\n" + entry.exception_msg
|
||||||
const step = debugData.value.trace[i]
|
: entry.exception_msg
|
||||||
if (step) {
|
|
||||||
if (step.event === "exception" || step.event === "uncaught_exception") {
|
|
||||||
if (step.exception_msg) {
|
|
||||||
outputText = step.exception_msg
|
|
||||||
}
|
|
||||||
} else if (step.stdout) {
|
|
||||||
outputText = step.stdout
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
outputText = outputText.trimEnd()
|
return outputText
|
||||||
|
})
|
||||||
|
|
||||||
const hasException = debugData.value.trace.some(
|
// 把外部状态的同步放到 watch 里(computed 不能有副作用)
|
||||||
(step: any) =>
|
watch(
|
||||||
step.event === "exception" || step.event === "uncaught_exception",
|
[currentOutput, () => debugData.value?.trace],
|
||||||
|
([text, trace]) => {
|
||||||
|
if (!trace) return
|
||||||
|
output.value = text
|
||||||
|
const hasException = trace.some(
|
||||||
|
(s: any) =>
|
||||||
|
s.event === "exception" ||
|
||||||
|
s.event === "uncaught_exception" ||
|
||||||
|
s.event === "instruction_limit_reached",
|
||||||
)
|
)
|
||||||
status.value = hasException ? Status.RuntimeError : Status.Accepted
|
status.value = hasException ? Status.RuntimeError : Status.Accepted
|
||||||
|
},
|
||||||
output.value = outputText
|
)
|
||||||
return outputText
|
|
||||||
}
|
|
||||||
return output.value || ""
|
|
||||||
})
|
|
||||||
|
|
||||||
// ==================== 主要功能函数 ====================
|
// ==================== 主要功能函数 ====================
|
||||||
/**
|
/**
|
||||||
@@ -330,14 +410,21 @@ function autoRun() {
|
|||||||
if (!debugData.value || !debugData.value.trace) return
|
if (!debugData.value || !debugData.value.trace) return
|
||||||
|
|
||||||
if (isAutoRunActive.value) {
|
if (isAutoRunActive.value) {
|
||||||
// 停止自动运行
|
|
||||||
pauseAutoRun()
|
pauseAutoRun()
|
||||||
isAutoRunning.value = false
|
isAutoRunning.value = false
|
||||||
} else {
|
return
|
||||||
// 开始自动运行
|
}
|
||||||
|
|
||||||
|
const trace = debugData.value.trace
|
||||||
|
// 已经到末尾或停在等待输入,没法继续自动运行
|
||||||
|
if (currentStep.value >= trace.length - 1) return
|
||||||
|
if (trace[currentStep.value]?.event === "raw_input") {
|
||||||
|
message.info("当前停在等待输入步,请先重新运行并提供输入")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
isAutoRunning.value = true
|
isAutoRunning.value = true
|
||||||
resumeAutoRun()
|
resumeAutoRun()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -23,41 +23,33 @@ function copy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查调试数据是否需要输入但用户没有提供足够的输入
|
* trace 末尾停在 raw_input 即说明输入不足
|
||||||
|
* (pg_logger 在缺输入时会立刻 done=True,trace 中至多只有 1 个 raw_input 事件,
|
||||||
|
* 所以不能用计数对比,只能看末尾)
|
||||||
*/
|
*/
|
||||||
function needsInputButNotProvided(
|
function endsAtRawInput(debugData: any): boolean {
|
||||||
debugData: any,
|
const trace = debugData?.trace
|
||||||
providedInputs: string[],
|
if (!trace?.length) return false
|
||||||
): boolean {
|
return trace[trace.length - 1].event === "raw_input"
|
||||||
if (!debugData?.trace || debugData.trace.length === 0) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastStep = debugData.trace[debugData.trace.length - 1]
|
|
||||||
// 如果最后一步是 raw_input,说明程序在等待输入,用户提供的输入不足
|
|
||||||
if (lastStep.event === "raw_input") {
|
|
||||||
// 统计 trace 中所有的 raw_input 事件数量(程序需要的输入数量)
|
|
||||||
const requiredInputCount = debugData.trace.filter(
|
|
||||||
(step: any) => step.event === "raw_input",
|
|
||||||
).length
|
|
||||||
|
|
||||||
// 如果用户提供的输入数量不足,返回 true
|
|
||||||
return providedInputs.length < requiredInputCount
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDebug() {
|
async function handleDebug() {
|
||||||
const inputs = input.value
|
const inputs = input.value
|
||||||
? input.value.split("\n").filter((line) => line.trim() !== "")
|
? input.value.split("\n").filter((line) => line.trim() !== "")
|
||||||
: []
|
: []
|
||||||
const res = await debug(code.value, inputs)
|
|
||||||
|
let res
|
||||||
|
try {
|
||||||
|
res = await debug(code.value, inputs)
|
||||||
|
} catch (err: any) {
|
||||||
|
message.error(`调试请求失败: ${err?.message ?? err}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
debugData.value = res.data
|
debugData.value = res.data
|
||||||
|
|
||||||
// 检查是否需要输入但用户没有提供足够的输入
|
if (endsAtRawInput(res.data)) {
|
||||||
if (needsInputButNotProvided(res.data, inputs)) {
|
message.warning("程序需要更多输入,请在输入框补全后重新点击调试")
|
||||||
message.warning("程序需要输入,请在输入框输入内容后重新点击调试按钮")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user