Skip to main content
Glama
parser_godot4.ts7.33 kB
export interface ErrorObject { file: string; line: number; message: string; stack: string[]; type: 'script_error' | 'parse_error' | 'runtime_error' | 'assertion_failed'; } export class ParserGodot4 { public parseFirstError(logOutput: string): ErrorObject | null { const lines = logOutput.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Parse script errors (most common) const scriptError = this.parseScriptError(line); if (scriptError) { scriptError.stack = this.extractStack(lines, i); return scriptError; } // Parse parser errors const parseError = this.parseParseError(line); if (parseError) { parseError.stack = this.extractStack(lines, i); return parseError; } // Parse runtime errors const runtimeError = this.parseRuntimeError(line); if (runtimeError) { runtimeError.stack = this.extractStack(lines, i); return runtimeError; } // Parse assertion failures const assertionError = this.parseAssertionError(line); if (assertionError) { assertionError.stack = this.extractStack(lines, i); return assertionError; } } return null; } private parseScriptError(line: string): ErrorObject | null { // Pattern: ERROR: Invalid call. Nonexistent function 'some_function' in base 'Node'. // At: res://scripts/combat/fighter.gd:123 @ _ready() const errorMatch = line.match(/ERROR:\s*(.+)/); if (!errorMatch) return null; const message = errorMatch[1]; // Look for "At: " line which usually follows const atMatch = message.match(/At:\s*([^:]+):(\d+)/); if (atMatch) { return { file: this.normalizeFilePath(atMatch[1]), line: parseInt(atMatch[2]), message: message.replace(/At:\s*[^:]+:\d+.*$/, '').trim(), stack: [], type: 'script_error' }; } // Alternative pattern: ERROR: res://path/file.gd:123 - Some error message const altMatch = line.match(/ERROR:\s*([^:]+):(\d+)\s*[-–]\s*(.+)/); if (altMatch) { return { file: this.normalizeFilePath(altMatch[1]), line: parseInt(altMatch[2]), message: altMatch[3], stack: [], type: 'script_error' }; } return null; } private parseParseError(line: string): ErrorObject | null { // Pattern: SCRIPT ERROR: Parse Error at line 123: Expected ')' after expression // Path: res://scripts/combat/fighter.gd const parseMatch = line.match(/SCRIPT ERROR:\s*Parse Error at line (\d+):\s*(.+)/); if (!parseMatch) return null; return { file: '', // Will be filled from next line typically line: parseInt(parseMatch[1]), message: `Parse Error: ${parseMatch[2]}`, stack: [], type: 'parse_error' }; } private parseRuntimeError(line: string): ErrorObject | null { // Pattern: SCRIPT ERROR: Invalid get index 'some_property' (on base: 'null instance') // At: res://scripts/combat/fighter.gd:123 @ some_function() const runtimeMatch = line.match(/SCRIPT ERROR:\s*(.+)/); if (!runtimeMatch) return null; const message = runtimeMatch[1]; const atMatch = message.match(/At:\s*([^:]+):(\d+)/); if (atMatch) { return { file: this.normalizeFilePath(atMatch[1]), line: parseInt(atMatch[2]), message: message.replace(/At:\s*[^:]+:\d+.*$/, '').trim(), stack: [], type: 'runtime_error' }; } return null; } private parseAssertionError(line: string): ErrorObject | null { // Pattern: ASSERTION FAILED: condition_name // At: res://scripts/combat/fighter.gd:123 const assertMatch = line.match(/ASSERTION FAILED:\s*(.+)/); if (!assertMatch) return null; return { file: '', // Will need to be extracted from context line: 0, // Will need to be extracted from context message: `Assertion failed: ${assertMatch[1]}`, stack: [], type: 'assertion_failed' }; } private extractStack(lines: string[], startIndex: number): string[] { const stack: string[] = []; // Look ahead for stack trace information for (let i = startIndex; i < Math.min(startIndex + 10, lines.length); i++) { const line = lines[i].trim(); // Stack frame patterns if (line.match(/^\s*at\s+/i) || line.match(/^[^:]+:\d+/)) { stack.push(line); } // Function call patterns if (line.includes(' @ ') || line.includes('()')) { const funcMatch = line.match(/([^@]+@[^(]+\([^)]*\))/); if (funcMatch) { stack.push(funcMatch[1]); } } // Stop at next error or empty line if ((line.startsWith('ERROR:') || line.startsWith('SCRIPT ERROR:')) && i > startIndex) { break; } if (line.length === 0 && stack.length > 0) { break; } } return stack; } private normalizeFilePath(filePath: string): string { // Convert absolute paths to res:// format if (filePath.startsWith('/') && !filePath.startsWith('res://')) { // Try to find project root indicator const resIndex = filePath.indexOf('/res/'); if (resIndex !== -1) { return 'res://' + filePath.substring(resIndex + 5); } } // Ensure res:// prefix if (!filePath.startsWith('res://') && !filePath.startsWith('/')) { return 'res://' + filePath; } return filePath; } public parseAllErrors(logOutput: string): ErrorObject[] { const errors: ErrorObject[] = []; const lines = logOutput.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; const error = this.parseScriptError(line) || this.parseParseError(line) || this.parseRuntimeError(line) || this.parseAssertionError(line); if (error) { error.stack = this.extractStack(lines, i); errors.push(error); } } return errors; } public isTestFailure(logOutput: string): boolean { const lowerOutput = logOutput.toLowerCase(); return lowerOutput.includes('failed') || lowerOutput.includes('error') || lowerOutput.includes('assertion'); } public extractTestResults(logOutput: string): { passed: number; failed: number; total: number; suites: string[]; } { const results = { passed: 0, failed: 0, total: 0, suites: [] as string[] }; // Parse gdUnit4 output patterns const passedMatch = logOutput.match(/(\d+)\s+passed/i); if (passedMatch) { results.passed = parseInt(passedMatch[1]); } const failedMatch = logOutput.match(/(\d+)\s+failed/i); if (failedMatch) { results.failed = parseInt(failedMatch[1]); } results.total = results.passed + results.failed; // Extract test suite names const suiteMatches = logOutput.matchAll(/Running\s+([A-Za-z0-9_]+Test)/g); for (const match of suiteMatches) { results.suites.push(match[1]); } return results; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Snack-JPG/Godot-Sentinel-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server