import { COMMENT, EXTENSION, parseTypeAndName, readDefinition, safeParseInt, syntaxError } from './common'; import { isCppAllowed } from './map'; import { Method } from './method'; import { SimpleProperty, StaticProperty } from './property'; import { Struct } from './struct'; // Error Handling export class ErrorOnLine { readonly error: unknown; readonly file: string; readonly line: number; constructor (error: unknown, file: string, line: number) { this.error = error; this.file = file; this.line = line; } } // Parse Property function parseProperty(args: string) { const parts = args.split(' = '); if (parts.length !== 2) { syntaxError('Invalid Piece Count'); } const {type, name} = parseTypeAndName(parts[0]!); const offset = safeParseInt(parts[1]!); return {type, name, offset}; } // Parse Method function parseMethod(args: string, self: string, insertSelfArg: boolean) { const argsStart = args.indexOf('('); if (argsStart === -1) { syntaxError('Cannot Find Arguments'); } const start = args.substring(0, argsStart).trim(); const {type, name} = parseTypeAndName(start); const end = args.substring(argsStart).trim().split(' = '); if (end.length !== 2) { syntaxError('Invalid Piece Count'); } let methodArgs = end[0]!; if (!methodArgs.startsWith('(') || !methodArgs.endsWith(')')) { syntaxError('Invalid Method Arguments'); } if (insertSelfArg) { let selfArg = `(${self} *self`; if (methodArgs !== '()') { selfArg += ', '; } methodArgs = selfArg + methodArgs.substring(1); } const address = safeParseInt(end[1]!); return new Method(self, name, type, methodArgs, address); } // Load Structure export function load(target: Struct, name: string, isExtended: boolean) { // Read File let data = readDefinition(name); // Strip Comments const lines = []; for (let line of data.split('\n')) { // Trim line = line.trim(); // Remove Comments const index = line.indexOf(COMMENT); if (index !== -1) { line = line.substring(0, index); } // Store Line lines.push(line); } data = lines.join('\n'); // Iterate Over Pieces let cursor = 0; for (let piece of data.split(';')) { // Find Start Of Command For Error Handling const startOfCommand = cursor + piece.search(/\S|$/); // Advance Cursor cursor += piece.length + 1; // Handle Errors try { // Trim piece = piece.trim(); // Replace Newlines With Spaces piece = piece.replace(/\n/g, ' '); // Simplify piece = piece.replace(/\s+/g, ' '); // Skip Empty Piece if (piece.length === 0) { continue; } // Skip C++ Types If Applicable if (!isCppAllowed() && piece.includes('std::')) { continue; } // Handle Commands let firstSpace = piece.indexOf(' '); if (firstSpace === -1) { firstSpace = piece.length; } const command = piece.substring(0, firstSpace); const args = piece.substring(firstSpace + 1); switch (command) { case 'extends': { // Load Parent load(target, args, true); break; } case 'size': { // Set Size if (!isExtended) { target.setSize(safeParseInt(args)); } break; } case 'vtable-size': { // Set VTable Size if (!isExtended) { target.setVTableSize(safeParseInt(args)); } break; } case 'vtable': { // Set VTable Address if (!isExtended) { target.setVTableAddress(safeParseInt(args)); } break; } case 'property': { // Add Property const info = parseProperty(args); target.addProperty(new SimpleProperty(info.offset, info.type, info.name)); break; } case 'static-property': { // Add Static Property if (!isExtended) { const info = parseProperty(args); target.addStaticProperty(new StaticProperty(info.offset, info.type, info.name, target.getName(), false)); } break; } case 'static-property-array': { // Add Static Array Property if (!isExtended) { const info = parseProperty(args); target.addStaticProperty(new StaticProperty(info.offset, info.type, info.name, target.getName(), true)); } break; } case 'method': { // Add Method const method = parseMethod(args, target.getName(), true); target.addMethod(method, false); break; } case 'virtual-method': { // Add Virtual Method const method = parseMethod(args, target.getName(), true); target.addMethod(method, true); break; } case 'static-method': { // Add Static Method if (!isExtended) { const method = parseMethod(args, target.getName(), false); target.addMethod(method, false); } break; } case 'constructor': { // Constructor if (!isExtended) { let data = `${target.getName()} *constructor`; if (args.startsWith('(')) { // No Custom Name data += ' '; } else { // Use Custom Name data += '_'; } data += args; const method = parseMethod(data, target.getName(), true); target.addMethod(method, false); } break; } default: { throw new Error(`Invalid Command: ${command}`); } } } catch (e) { if (e instanceof ErrorOnLine) { throw e; } // Find Line Number let lineNumber = 1; for (let i = 0; i <= startOfCommand; i++) { if (data.charAt(i) === '\n') { lineNumber++; } } // Rethrow Error With Line Number throw new ErrorOnLine(e, `${name}${EXTENSION}`, lineNumber); } } }