symbol-processor/src/struct.ts

281 lines
8.9 KiB
TypeScript

import { INDENT, STRUCTURE_FILES, toHex, assertSize, formatType, getArgNames, removeFirstArg, ORIGINAL_SUFFIX } from './common';
import { Method } from './method';
import { Property, StaticProperty } from './property';
import { VTable } from './vtable';
export class Struct {
readonly #name: string;
#vtable: VTable | null;
readonly #methods: Method[];
readonly #properties: Property[];
#size: number | null;
readonly #dependencies: string[];
readonly #staticProperties: StaticProperty[];
#directParent: string | null;
// Constructor
constructor(name: string) {
this.#name = name;
this.#methods = [];
this.#properties = [];
this.#vtable = null;
this.#size = null;
this.#dependencies = [];
this.#staticProperties = [];
this.#directParent = null;
}
// Dependencies
#addDependency(dependency: string) {
this.#dependencies.push(dependency);
}
getDependencies() {
return this.#dependencies;
}
// Ensure VTable Exists
ensureVTable() {
if (this.#vtable === null) {
this.#vtable = new VTable(this.#name);
this.addProperty(this.#vtable.property);
}
}
// Set VTable Destructor Offset
setVTableDestructorOffset(offset: number) {
this.ensureVTable();
this.#vtable!.setDestructorOffset(offset);
}
// Setters
setSize(size: number) {
this.#size = size;
}
// Getters
getName() {
return this.#name;
}
// Add Method
addMethod(method: Method, isVirtual: boolean) {
if (method.returnType !== this.#name && method.returnType in STRUCTURE_FILES) {
this.#addDependency(method.returnType);
}
if (isVirtual) {
if (method.self !== this.#name) {
throw new Error();
}
this.ensureVTable();
this.#vtable!.add(method);
} else {
if (method.isInherited) {
this.#addDependency(method.self);
}
this.#methods.push(method);
}
}
// Add Property
addProperty(property: Property) {
this.#properties.push(property);
// Add Dependency If Needed
const type = property.rawType();
if (type in STRUCTURE_FILES) {
this.#addDependency(type);
}
}
// Add Static Property
addStaticProperty(property: StaticProperty) {
this.#staticProperties.push(property);
}
// Configure VTable
setVTableSize(size: number) {
this.ensureVTable();
this.#vtable!.setSize(size);
}
setVTableAddress(address: number) {
this.ensureVTable();
this.#vtable!.setAddress(address);
}
// Generate Properties
#generateProperties() {
// Sort Properties
const sortedProperties = [];
sortedProperties.push(...this.#properties);
sortedProperties.sort((a, b) => a.offset - b.offset);
// Fake Property To Pad Structure Size
let sizeProperty = null;
if (this.#size) {
sizeProperty = new Property(this.#size, '', '', '');
sortedProperties.push(sizeProperty);
}
// Add Properties
let out = '';
for (let i = 0; i < sortedProperties.length; i++) {
const property = sortedProperties[i]!;
const lastProperty = sortedProperties[i - 1];
// Padding
let offsetFromLastProperty = property.offset;
if (lastProperty) {
offsetFromLastProperty -= lastProperty.offset;
}
let paddingSize = toHex(offsetFromLastProperty);
if (lastProperty) {
paddingSize += ` - sizeof(${lastProperty.type()})`;
}
out += `${INDENT}uchar __padding${i}[${paddingSize}];\n`;
// The Actual Property
if (property !== sizeProperty) {
out += `${INDENT}${property.type()} ${property.name()};\n`;
}
}
return out;
}
// Generate C++ Method Shortcuts
#generateMethods() {
let out = '';
// Normal Methods
const getArgsOuter = (method: Method) => {
let out = method.args;
if (!method.isStatic) {
// Remove "self"
out = removeFirstArg(out);
}
return out;
};
const getArgsInner = (method: Method) => {
const list = getArgNames(method.args);
if (!method.isStatic) {
// Replace "self" With "this"
list[0] = `(${method.self} *) this`;
}
return list.join(', ');
};
for (const method of this.#methods) {
if (!method.hasVarargs) {
const returnType = method.returnType;
const shortName = method.shortName;
const fullName = method.getName();
const args = getArgsOuter(method);
out += `${INDENT}${method.isStatic ? 'static ' : ''}inline ${formatType(returnType)}${shortName}${args} { \\\n`;
out += `${INDENT}${INDENT}${returnType.trim() === 'void' ? '' : 'return '}${fullName}(${getArgsInner(method)});\n`;
out += `${INDENT}}\n`;
}
}
// Virtual Methods
if (this.#vtable !== null) {
const virtualMethods = this.#vtable.getMethods();
for (const method of virtualMethods) {
if (method && !method.hasVarargs) {
const returnType = method.returnType;
const shortName = method.shortName;
const args = getArgsOuter(method);
out += `${INDENT}inline ${formatType(returnType)}${shortName}${args} { \\\n`;
out += `${INDENT}${INDENT}${returnType.trim() === 'void' ? '' : 'return '}this->vtable->${shortName}(${getArgsInner(method)});\n`;
out += `${INDENT}}\n`;
}
}
}
// Return
return out;
}
// Generate Header
generate() {
let out = '';
// Static Properties
for (const property of this.#staticProperties) {
out += property.typedef();
}
// Methods
for (const method of this.#methods) {
if (!method.isInherited) {
out += method.generateTypedef();
out += `extern ${method.generateDefinition()};\n`;
out += `extern ${method.generateDefinition(ORIGINAL_SUFFIX)};\n`;
}
}
// VTable
if (this.#vtable !== null) {
out += this.#vtable.generate();
}
// Property Typedefs
for (const property of this.#properties) {
out += property.typedef();
}
// Structure
out += `struct ${this.#name} {\n`;
out += this.#generateProperties();
out += this.#generateMethods();
for (const property of this.#staticProperties) {
// Static Property References
out += `${INDENT}static ${property.referenceDefinition(false)};\n`;
}
if (this.#size === null) {
// Prevent Manually Copying/Allocating Structure With Undefined
out += `${INDENT}${this.#name}() = delete;\n`;
out += `${INDENT}${this.#name}(const ${this.#name} &) = delete;\n`;
out += `${INDENT}${this.#name} &operator=(const ${this.#name} &) = delete;\n`;
}
out += `};\n`;
// Sanity Check Offsets
for (let i = 0; i < this.#properties.length; i++) {
const property = this.#properties[i]!;
const name = property.name();
const offset = property.offset;
out += `static_assert(offsetof(${this.#name}, ${name}) == ${toHex(offset)}, "Invalid Offset");\n`;
}
// Sanity Check Size
if (this.#size !== null) {
out += assertSize(this.#name, this.#size);
}
// Return
return out;
}
// Generate Compiled Code
generateCode() {
let out = '';
// Static Properties
for (const property of this.#staticProperties) {
out += `${property.referenceDefinition(true)} = *(${property.type()} *) ${toHex(property.offset)};\n`;
}
// Methods
for (const method of this.#methods) {
if (!method.isInherited) {
out += `${method.generateDefinition()} = (${method.getType()}) ${toHex(method.address)};\n`;
out += `${method.generateDefinition(ORIGINAL_SUFFIX)} = ${method.getName()};\n`;
}
}
// VTable
if (this.#vtable !== null) {
const vtable = this.#vtable.generateCode(this.#directParent);
out += vtable;
}
// Return
return out;
}
// Set Direct Parent (Used For "New Method" Testing)
setDirectParent(directParent: string) {
this.#directParent = directParent;
}
}