import { INDENT, STRUCTURE_FILES, toHex, assertSize, INTERNAL, preventConstruction, LEAN_HEADER_GUARD } 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; #isSimple: boolean; // Constructor constructor(name: string) { this.name = name; this.#methods = []; this.#properties = []; this.#vtable = null; this.#size = null; this.#dependencies = []; this.#staticProperties = []; this.#directParent = null; this.#isSimple = false; } // Dependencies #addDependency(dependency: string) { this.#dependencies.push(dependency); } getDependencies() { return this.#dependencies; } // Ensure VTable Exists getVTable() { if (this.#vtable === null) { this.#vtable = new VTable(this.name); this.addProperty(this.#vtable.property); } return this.#vtable; } // Setters setSize(size: number) { this.#size = size; } // 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.getVTable().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); } // "Simple" Structures markAsSimple() { this.#isSimple = true; } #checkSimple() { const checks = [ ['Cannot Inherit', this.#directParent !== null], ['Must Have A Defined Size', this.#size === null], ['Cannot Have A VTable', this.#vtable !== null] ]; for (const check of checks) { if (check[1]) { throw new Error('Simple Structures ' + check[0]); } } } // 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()})`; } if (!this.#isSimple) { out += `${INDENT}uchar ${INTERNAL}padding${i}[${paddingSize}];\n`; } // The Actual Property if (property !== sizeProperty) { out += `${INDENT}${property.type()} ${property.name()};\n`; } } return out; } // Generate C++ Method Shortcuts #generateMethod(method: Method, isVirtual: boolean) { let out = ''; out += INDENT; if (method.isStatic) { out += 'static '; } out += `decltype(auto) ${method.shortName}(auto&&... args) {\n`; out += `${INDENT}${INDENT}return `; if (isVirtual) { out += `this->vtable->${method.shortName}`; } else { out += `${method.getName()}->get(false)`; } out += '('; if (!method.isStatic) { out += `(${method.self} *) this, `; } out += 'std::forward(args)...);\n'; out += `${INDENT}}\n`; return out; } #generateMethods() { let out = ''; out += LEAN_HEADER_GUARD; // Normal Methods for (const method of this.#methods) { out += this.#generateMethod(method, false); } // Virtual Methods if (this.#vtable !== null) { const virtualMethods = this.#vtable.getMethods(); for (const method of virtualMethods) { if (method) { out += this.#generateMethod(method, true); } } } // Allocation Method if (this.#size !== null) { // THIS DOES NOT CONSTRUCT THE OBJECT out += `${INDENT}static ${this.name} *allocate() {\n`; out += `${INDENT}${INDENT}return (${this.name} *) ::operator new(sizeof(${this.name}));\n`; out += `${INDENT}}\n`; } // Return out += '#endif\n'; return out; } // Generate Header generate() { let out = ''; // Check "Simple" Status if (this.#isSimple) { this.#checkSimple(); } // VTable if (this.#vtable !== null) { out += this.#vtable.generate(); } // Static Properties for (const property of this.#staticProperties) { out += property.typedef(); } // Property Typedefs for (const property of this.#properties) { out += property.typedef(); } // Method Wrappers let typedefs = ''; let methodsOut = ''; for (const method of this.#methods) { if (!method.isInherited) { typedefs += method.generateTypedefs(); methodsOut += method.generate(false, false); } } out += typedefs; out += LEAN_HEADER_GUARD; out += methodsOut; out += '#endif\n'; // Structure out += `struct ${this.name} final {\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.#isSimple) { // Disable Construction Of Complex Structures out += preventConstruction(this.name); } 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.generate(true, false); } } // 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; } }