2024-04-03 07:18:51 +00:00
|
|
|
import { COMMENT, EXTENSION, getSelfArg, parseTypeAndName, prependArg, readDefinition, safeParseInt, syntaxError } from './common';
|
2024-01-04 20:27:02 +00:00
|
|
|
import { Method } from './method';
|
|
|
|
import { SimpleProperty, StaticProperty } from './property';
|
|
|
|
import { Struct } from './struct';
|
|
|
|
|
2024-01-04 22:00:19 +00:00
|
|
|
// Error Handling
|
2024-01-04 20:27:02 +00:00
|
|
|
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) {
|
2024-01-12 03:09:34 +00:00
|
|
|
const parts = args.split(' = ');
|
|
|
|
if (parts.length !== 2) {
|
2024-01-04 20:27:02 +00:00
|
|
|
syntaxError('Invalid Piece Count');
|
|
|
|
}
|
2024-01-12 03:09:34 +00:00
|
|
|
const {type, name} = parseTypeAndName(parts[0]!);
|
|
|
|
const offset = safeParseInt(parts[1]!);
|
2024-01-04 20:27:02 +00:00
|
|
|
return {type, name, offset};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse Method
|
2024-04-03 07:18:51 +00:00
|
|
|
function parseMethod(args: string, self: string, insertSelfArg: boolean, isInherited: boolean) {
|
2024-01-06 09:06:00 +00:00
|
|
|
const argsStart = args.indexOf('(');
|
2024-01-04 20:27:02 +00:00
|
|
|
if (argsStart === -1) {
|
|
|
|
syntaxError('Cannot Find Arguments');
|
|
|
|
}
|
2024-01-12 03:09:34 +00:00
|
|
|
const start = args.substring(0, argsStart).trim();
|
|
|
|
const {type, name} = parseTypeAndName(start);
|
2024-01-06 09:06:00 +00:00
|
|
|
const end = args.substring(argsStart).trim().split(' = ');
|
2024-01-04 20:27:02 +00:00
|
|
|
if (end.length !== 2) {
|
|
|
|
syntaxError('Invalid Piece Count');
|
|
|
|
}
|
|
|
|
let methodArgs = end[0]!;
|
|
|
|
if (!methodArgs.startsWith('(') || !methodArgs.endsWith(')')) {
|
|
|
|
syntaxError('Invalid Method Arguments');
|
|
|
|
}
|
|
|
|
if (insertSelfArg) {
|
2024-04-03 07:18:51 +00:00
|
|
|
const selfArg = getSelfArg(self);
|
|
|
|
methodArgs = prependArg(methodArgs, selfArg);
|
2024-01-04 20:27:02 +00:00
|
|
|
}
|
|
|
|
const address = safeParseInt(end[1]!);
|
2024-04-03 07:18:51 +00:00
|
|
|
return new Method(self, name, type, methodArgs, address, isInherited);
|
2024-01-04 20:27:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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');
|
|
|
|
|
2024-01-06 09:06:00 +00:00
|
|
|
// Iterate Over Pieces
|
2024-01-04 20:27:02 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2024-04-03 07:18:51 +00:00
|
|
|
if (!isExtended) {
|
|
|
|
target.setDirectParent(args);
|
|
|
|
}
|
2024-01-04 20:27:02 +00:00
|
|
|
load(target, args, true);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'size': {
|
|
|
|
// Set Size
|
2024-02-01 19:31:25 +00:00
|
|
|
target.setSize(safeParseInt(args), !isExtended);
|
2024-01-04 20:27:02 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'vtable-size': {
|
|
|
|
// Set VTable Size
|
2024-02-01 19:31:25 +00:00
|
|
|
target.setVTableSize(safeParseInt(args), !isExtended);
|
2024-01-04 20:27:02 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'vtable': {
|
|
|
|
// Set VTable Address
|
2024-02-01 19:55:27 +00:00
|
|
|
target.ensureVTable();
|
|
|
|
if (!isExtended && args.length > 0) {
|
2024-01-04 20:27:02 +00:00
|
|
|
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);
|
2024-01-07 07:41:08 +00:00
|
|
|
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));
|
2024-01-04 20:27:02 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'method': {
|
|
|
|
// Add Method
|
2024-04-03 07:18:51 +00:00
|
|
|
const method = parseMethod(args, target.getName(), true, isExtended);
|
2024-01-04 20:27:02 +00:00
|
|
|
target.addMethod(method, false);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'virtual-method': {
|
|
|
|
// Add Virtual Method
|
2024-04-03 07:18:51 +00:00
|
|
|
const method = parseMethod(args, target.getName(), true, isExtended);
|
2024-01-04 20:27:02 +00:00
|
|
|
target.addMethod(method, true);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'static-method': {
|
|
|
|
// Add Static Method
|
|
|
|
if (!isExtended) {
|
2024-04-03 07:18:51 +00:00
|
|
|
const method = parseMethod(args, target.getName(), false, false);
|
2024-01-04 20:27:02 +00:00
|
|
|
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;
|
2024-04-03 07:18:51 +00:00
|
|
|
const method = parseMethod(data, target.getName(), true, false);
|
2024-01-04 20:27:02 +00:00
|
|
|
target.addMethod(method, false);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2024-04-03 07:18:51 +00:00
|
|
|
case 'vtable-destructor-offset': {
|
|
|
|
// Set VTable Destructor Offset
|
|
|
|
target.setVTableDestructorOffset(safeParseInt(args));
|
|
|
|
break;
|
|
|
|
}
|
2024-01-04 20:27:02 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|