update
Some checks failed
Deploy / build-and-deploy (push) Has been cancelled

This commit is contained in:
2026-05-19 08:04:11 -06:00
parent a480bda71b
commit 678d3d9abf
2 changed files with 171 additions and 92 deletions

View File

@@ -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,15 +410,22 @@ 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>
<template> <template>

View File

@@ -23,41 +23,33 @@ function copy() {
} }
/** /**
* 检查调试数据是否需要输入但用户没有提供足够的输入 * trace 末尾停在 raw_input 即说明输入不足
* pg_logger 在缺输入时会立刻 done=Truetrace 中至多只有 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
} }