File size: 2,649 Bytes
1f55966 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | import type { ManimApiRequest } from '../manim-api'
import { extractTargetLine } from './utils'
const IDENTIFIER = '[A-Za-z_]\\w*'
function inferVariableTypes(code: string): Map<string, string> {
const types = new Map<string, string>()
const assignment = new RegExp(`^\\s*(${IDENTIFIER})\\s*(?::[^=]+)?=\\s*(${IDENTIFIER})\\s*\\(`)
for (const line of code.split(/\r?\n/)) {
const match = line.match(assignment)
if (match && /^[A-Z]/.test(match[2])) {
types.set(match[1], match[2])
}
}
return types
}
function sourceLineFromError(code: string, errorContext: string): string | undefined {
for (const line of errorContext.split(/\r?\n/).reverse()) {
const richTracebackLine = line.match(/^[│|]\s*(?:❱|>)\s*\d+\s+(.*?)\s*[│|]\s*$/)
if (richTracebackLine?.[1]) return richTracebackLine[1]
}
const lineNumber = extractTargetLine(errorContext)
if (lineNumber) {
const line = code.split(/\r?\n/)[lineNumber - 1]
if (line?.trim()) return line
}
return undefined
}
function requestFor(owner: string, member: string, reason: string): ManimApiRequest {
return {
query: `${reason}. Verify the runtime API and find the intended callable replacement.`,
symbols: [`${owner}.${member}`]
}
}
/**
* Extracts high-confidence API lookup keys from a Python traceback and its source.
* This is deliberately syntax-oriented: the runtime catalog supplies the facts.
*/
export function inferAutomaticApiRequests(
code: string,
errorContext: string
): ManimApiRequest[] {
const requests: ManimApiRequest[] = []
const seen = new Set<string>()
const add = (request: ManimApiRequest) => {
const key = request.symbols[0].toLowerCase()
if (!seen.has(key)) {
seen.add(key)
requests.push(request)
}
}
const attributeError = errorContext.match(
/['"]?([A-Za-z_]\w*)['"]?\s+object has no attribute\s+['"]([A-Za-z_]\w*)['"]/i
)
if (attributeError) {
add(requestFor(attributeError[1], attributeError[2], 'The traceback reports a missing attribute'))
}
const failingLine = sourceLineFromError(code, errorContext)
if (!failingLine) return requests
const variableTypes = inferVariableTypes(code)
const calls = failingLine.matchAll(
new RegExp(`\\b(${IDENTIFIER})((?:\\.${IDENTIFIER})+)\\s*\\(`, 'g')
)
for (const call of calls) {
const root = call[1]
const chain = call[2].slice(1).split('.')
const member = chain[chain.length - 1]
const owner = variableTypes.get(root)
if (owner) {
add(requestFor(owner, member, `The failing expression is ${root}${call[2]}(...)`))
}
}
return requests.slice(0, 3)
}
|