import { INDENT, POINTER_SIZE } from './common'; import { Method } from './method'; import { Property } from './property'; export class VTable implements Property { readonly #name: string; #address: number | null; #size: number | null; readonly #methods: Method[]; // Constructor constructor(name: string) { this.#name = name; this.#address = null; this.#size = null; this.#methods = []; } // Property Information propertyOffset() { return 0; } propertySize() { return POINTER_SIZE; } propertyType() { return this.#getName() + ' *'; } propertyName() { return 'vtable'; } propertyAlignment() { return 4; } // Setters setAddress(address: number) { this.#address = address; } setSize(size: number) { this.#size = size; if ((this.#size % POINTER_SIZE) !== 0) { throw new Error(`Invalid VTable Size: ${this.#size}`); } } // Add To VTable add(method: Method) { // Check Offset const offset = method.address; if ((offset % POINTER_SIZE) !== 0) { throw new Error(`Invalid VTable Offset: ${offset}`); } // Check Size if (this.#size !== null && (offset + POINTER_SIZE) > this.#size) { throw new Error(`VTable Offset Too Large: ${offset}`); } // Add const index = offset / POINTER_SIZE; this.#methods[index] = method; } // Get Structure Name #getName() { return this.#name + '_vtable'; } // Check #check() { // Check Size if (this.#size !== null) { const maxMethodCount = this.#size / POINTER_SIZE; if (maxMethodCount < this.#methods.length) { throw new Error(`VTable Size Too Small: ${this.#size}`); } this.#methods.length = maxMethodCount; } } // Generate Code generate() { let out = ''; // Check this.#check(); // Method Prototypes for (let i = 0; i < this.#methods.length; i++) { const info = this.#methods[i]; if (info) { out += info.generateTypedef(); } } // Structure out += `typedef struct ${this.#getName()} ${this.#getName()};\n`; out += `struct ${this.#getName()} {\n`; for (let i = 0; i < this.#methods.length; i++) { let name = `unknown${i}`; let type = 'void *'; const info = this.#methods[i]; if (info) { name = info.shortName; type = info.getType() + ' '; } out += `${INDENT}${type}${name};\n`; } out += `};\n`; // Pointers if (this.#address !== null) { // Base out += `extern ${this.#getName()} *${this.#getName()}_base;\n`; // Methods for (let i = 0; i < this.#methods.length; i++) { const info = this.#methods[i]; if (info) { const type = `${info.getType()} *`; out += `extern ${type}${info.getName()}_vtable_addr;\n`; out += `extern ${info.generateDefinition()}`; } } } // Duplication Method if (this.#size !== null) { out += `${this.#getName()} *dup_${this.#getName()}(${this.#getName()} *vtable);\n`; } // Return return out; } // Generate Compiled Code generateCode() { let declarations = ''; let init = ''; // Check this.#check(); // Pointers if (this.#address !== null) { // Base init += `${INDENT}${this.#getName()}_base = (${this.#getName()} *) ${this.#address};\n`; declarations += `${this.#getName()} *${this.#getName()}_base;\n`; // Methods for (let i = 0; i < this.#methods.length; i++) { const info = this.#methods[i]; if (info) { const vtableAddress = this.#address + (i * POINTER_SIZE); const type = `${info.getType()} *`; init += `${INDENT}${info.getName()}_vtable_addr = (${type}) ${vtableAddress};\n`; declarations += `${type}${info.getName()}_vtable_addr;\n`; init += `${INDENT}${info.getName()} = *${info.getName()}_vtable_addr;\n`; declarations += info.generateDefinition(); } } } // Duplication Method if (this.#size !== null) { declarations += `${this.#getName()} *dup_${this.#getName()}(${this.#getName()} *vtable) {\n`; declarations += `${INDENT}${this.#getName()} *obj = (${this.#getName()} *) malloc(${this.#size});\n`; declarations += `${INDENT}if (obj == NULL) {\n`; declarations += `${INDENT}${INDENT}return NULL;\n`; declarations += `${INDENT}}\n`; declarations += `${INDENT}memcpy((void *) obj, (void *) vtable, ${this.#size});\n`; declarations += `${INDENT}return obj;\n`; declarations += '}\n'; } // Return return {declarations, init}; } }