From 818b7535ebb2dad904682426384f3b07202f45fe Mon Sep 17 00:00:00 2001 From: TheBrokenRail Date: Sun, 30 Mar 2025 22:49:51 -0400 Subject: [PATCH] Simplify Generated Code --- data/function.h | 1 + data/internal.h | 6 +++- data/out.cpp | 4 +++ data/out.h | 5 +++- src/common.ts | 68 +++++++++++++++++++++++++++++++++++++++++++++ src/loader.ts | 8 ++---- src/method.ts | 19 ++++++++++--- src/property.ts | 29 ++++++++----------- src/struct.ts | 58 ++++++++++++++++++-------------------- src/test/parsing.ts | 49 ++++++++++++++++++++++++++++++-- src/vtable.ts | 16 +++++------ 11 files changed, 192 insertions(+), 71 deletions(-) diff --git a/data/function.h b/data/function.h index 21ccc5d..394b92a 100644 --- a/data/function.h +++ b/data/function.h @@ -10,6 +10,7 @@ public: [[nodiscard]] virtual type get() const = 0; [[nodiscard]] virtual type *get_addr() const = 0; virtual void update(type new_func) = 0; + virtual ~__FunctionInfo() = default; }; // Thunks diff --git a/data/internal.h b/data/internal.h index 2befb23..daa7875 100644 --- a/data/internal.h +++ b/data/internal.h @@ -7,4 +7,8 @@ __PREVENT_DESTRUCTION(self) #define __PREVENT_COPY(self) \ self(const self &) = delete; \ - self &operator=(const self &) = delete \ No newline at end of file + self &operator=(const self &) = delete +template +static constexpr decltype(sizeof(T)) __real_sizeof() { + return sizeof(std::conditional_t, void *, T>); +} \ No newline at end of file diff --git a/data/out.cpp b/data/out.cpp index 46e248b..b1cf295 100644 --- a/data/out.cpp +++ b/data/out.cpp @@ -63,4 +63,8 @@ public: }; #undef super +// Disable Warnings +#pragma GCC diagnostic ignored "-Wparentheses" +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + {{ main }} \ No newline at end of file diff --git a/data/out.h b/data/out.h index ab181bb..2dcec86 100644 --- a/data/out.h +++ b/data/out.h @@ -5,6 +5,9 @@ #error "Symbols Are ARM-Only" #endif +// Type Traits +#include + // Internal Macros {{ include internal.h }} @@ -31,8 +34,8 @@ typedef unsigned int uint; // Warnings #pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Winvalid-offsetof" #pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wparentheses" {{ main }} diff --git a/src/common.ts b/src/common.ts index d089432..f6bcd31 100644 --- a/src/common.ts +++ b/src/common.ts @@ -55,7 +55,13 @@ export function parseTypeAndName(piece: string) { } // Trim name = name.trim(); + if (name.length === 0) { + syntaxError(); + } type = type.trim(); + if (type.length === 0) { + syntaxError(); + } // Return return {type, name}; } @@ -84,6 +90,7 @@ export function toUpperSnakeCase(str: string) { } // Convert 'int' To 'int ' But Leave 'int *' As Is export function formatType(type: string) { + type = type.trim(); if (!POINTER_TOKENS.some(x => type.endsWith(x))) { type += ' '; } @@ -150,4 +157,65 @@ export function preventConstruction(self: string) { out += `${INDENT}${INTERNAL}PREVENT_CONSTRUCTION(${self});\n`; out += `${INDENT}${INTERNAL}PREVENT_COPY(${self});\n`; return out; +} +// Remove Array Information From Variable Name +export function extractArrayInfo(name: string) { + let arrayInfo = ''; + const arrayInfoIndex = name.indexOf('['); + if (arrayInfoIndex !== -1) { + arrayInfo = name.substring(arrayInfoIndex); + name = name.substring(0, arrayInfoIndex); + } + return { + name, + arrayInfo + }; +} +// Forward Function Arguments Into Call +export function forwardArguments(args: string, extra: string[]) { + const separator = ','; + const openingParen = '('; + const opening = ['<', openingParen, '[']; + const closingParen = ')'; + const closing = ['>', closingParen, ']']; + // Remove Enclosing Parenthesis + args = args.trim(); + if (!args.startsWith(openingParen) || !args.endsWith(closingParen)) { + syntaxError(); + } + args = args.substring(openingParen.length, args.length - closingParen.length); + // Parse + const parts: string[] = []; + let start = 0; + const stack = []; + for (let i = 0; i < args.length; i++) { + const c = args.charAt(i); + if (stack.length === 0 && c === separator) { + parts.push(args.substring(start, i)); + start = i + 1; + } else if (opening.includes(c)) { + stack.push(c); + } else { + const i = closing.indexOf(c); + if (i !== -1) { + const x = stack.pop(); + if (x !== opening[i]) { + syntaxError(); + } + } + } + } + if (stack.length !== 0) { + syntaxError(); + } + if (start < args.length) { + parts.push(args.substring(start)); + } + // Handle + const out: string[] = []; + for (const part of parts) { + out.push(extractArrayInfo(parseTypeAndName(part.trim()).name).name); + } + out.unshift(...extra); + return openingParen + out.join(separator + ' ') + closingParen; } \ No newline at end of file diff --git a/src/loader.ts b/src/loader.ts index 35a0bce..b6e7f9d 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -1,4 +1,4 @@ -import { COMMENT, extendErrorMessage, EXTENSION, getSelfArg, parseTypeAndName, prependArg, readDefinition, safeParseInt, syntaxError } from './common'; +import { COMMENT, extendErrorMessage, EXTENSION, parseTypeAndName, readDefinition, safeParseInt, syntaxError } from './common'; import { Method } from './method'; import { Property, StaticProperty } from './property'; import { Struct } from './struct'; @@ -43,14 +43,10 @@ export function parseMethod(args: string, self: string, insertSelfArg: boolean, if (!end[0] || !end[1]) { syntaxError('Invalid Piece Count'); } - let methodArgs = end[0].trim(); + const methodArgs = end[0].trim(); if (!methodArgs.startsWith('(') || !methodArgs.endsWith(')')) { syntaxError('Invalid Method Arguments'); } - if (insertSelfArg) { - const selfArg = getSelfArg(self); - methodArgs = prependArg(methodArgs, selfArg); - } const address = safeParseInt(end[1]); return new Method(self, name, type, methodArgs, address, isInherited, !insertSelfArg); } diff --git a/src/method.ts b/src/method.ts index dcc9a44..84a7f2c 100644 --- a/src/method.ts +++ b/src/method.ts @@ -1,4 +1,4 @@ -import { INDENT, INTERNAL, formatType, toHex } from './common'; +import { INDENT, INTERNAL, formatType, getSelfArg, prependArg, toHex } from './common'; // A Template Parameter So Each Template Instantiation Is Unique let nextDiscriminator = 0; @@ -9,7 +9,7 @@ export class Method { readonly self: string; readonly shortName: string; readonly returnType: string; - readonly args: string; + readonly #args: string; readonly address: number; readonly isInherited: boolean; readonly isStatic: boolean; @@ -20,7 +20,7 @@ export class Method { this.self = self; this.shortName = name; this.returnType = returnType; - this.args = args; + this.#args = args; this.address = address; this.isInherited = isInherited; this.isStatic = isStatic; @@ -42,11 +42,22 @@ export class Method { #getFullType() { return `${INTERNAL}Function<${this.#discriminator.toString()}, ${this.#getRawType()}>`; } + doesReturnValue() { + return this.returnType.trim() !== 'void'; + } + getArgs(includeSelf = true) { + let args = this.#args.trim(); + if (includeSelf && !this.isStatic) { + const selfArg = getSelfArg(this.self); + args = prependArg(args, selfArg); + } + return args; + } // Typedefs generateTypedefs() { let out = ''; - out += `typedef ${formatType(this.returnType.trim())}${this.#getRawType()}${this.args.trim()};\n`; + out += `typedef ${formatType(this.returnType)}${this.#getRawType()}${this.getArgs()};\n`; out += `typedef const std::function<${this.#getRawType()}> &${this.getType()};\n`; return out; } diff --git a/src/property.ts b/src/property.ts index c422a66..a49396b 100644 --- a/src/property.ts +++ b/src/property.ts @@ -1,4 +1,4 @@ -import { INTERNAL, formatType } from './common'; +import { extractArrayInfo, formatType } from './common'; // A Single Property export class Property { @@ -16,24 +16,16 @@ export class Property { } // Getters - type() { - return `${INTERNAL}type_${this.fullName()}`; - } - typedef() { - let arrayInfo = ''; - const arrayInfoIndex = this.#name.indexOf('['); - if (arrayInfoIndex !== -1) { - arrayInfo = this.#name.substring(arrayInfoIndex); - } - return `typedef ${formatType(this.#type)}${this.type()}${arrayInfo};\n`; + parts() { + const info = extractArrayInfo(this.#name); + return { + type: formatType(this.#type), + name: info.name, + arrayInfo: info.arrayInfo + }; } name() { - let name = this.#name; - const arrayInfoIndex = this.#name.indexOf('['); - if (arrayInfoIndex !== -1) { - name = name.substring(0, arrayInfoIndex); - } - return name; + return this.parts().name; } #fullName(separator: string) { return this.#self + separator + this.name(); @@ -52,6 +44,7 @@ export class Property { export class StaticProperty extends Property { // Reference referenceDefinition(addSelf: boolean) { - return `${this.type()} &${addSelf ? this.prettyName() : this.name()}`; + const parts = this.parts(); + return `${parts.type}(&${addSelf ? this.prettyName() : this.name()})${parts.arrayInfo}`; } } \ No newline at end of file diff --git a/src/struct.ts b/src/struct.ts index c0bbb65..d863dd1 100644 --- a/src/struct.ts +++ b/src/struct.ts @@ -1,4 +1,4 @@ -import { INDENT, STRUCTURE_FILES, toHex, assertSize, INTERNAL, preventConstruction, BUILDING_SYMBOLS_GUARD } from './common'; +import { INDENT, STRUCTURE_FILES, toHex, assertSize, INTERNAL, preventConstruction, BUILDING_SYMBOLS_GUARD, formatType, forwardArguments } from './common'; import { Method } from './method'; import { Property, StaticProperty } from './property'; import { VTable } from './vtable'; @@ -124,7 +124,8 @@ export class Struct { } let paddingSize = toHex(offsetFromLastProperty); if (lastProperty) { - paddingSize += ` - sizeof(${lastProperty.type()})`; + const parts = lastProperty.parts(); + paddingSize += ` - ${INTERNAL}real_sizeof<${parts.type.trim()}${parts.arrayInfo}>()`; } if (!this.#isSimple) { out += `${INDENT}uchar ${INTERNAL}padding${i.toString()}[${paddingSize}];\n`; @@ -132,7 +133,8 @@ export class Struct { // The Actual Property if (property !== sizeProperty) { - out += `${INDENT}${property.type()} ${property.name()};\n`; + const parts = property.parts(); + out += `${INDENT}${parts.type}${parts.name}${parts.arrayInfo};\n`; } // Advance @@ -149,18 +151,22 @@ export class Struct { if (method.isStatic) { out += 'static '; } - out += `decltype(auto) ${method.shortName}(auto&&... args) {\n`; - out += `${INDENT}${INDENT}return `; + const args = method.getArgs(false); + out += formatType(method.returnType) + method.shortName + args + ' {\n'; + out += INDENT + INDENT; + if (method.doesReturnValue()) { + out += 'return '; + } if (isVirtual) { out += `this->vtable->${method.shortName}`; } else { out += `${method.getName()}->get(false)`; } - out += '('; + const extra = []; if (!method.isStatic) { - out += `(${method.self} *) this, `; + extra.push(`(${method.self} *) this`); } - out += 'std::forward(args)...);\n'; + out += forwardArguments(args, extra) + ';\n'; out += `${INDENT}}\n`; return out; } @@ -206,16 +212,6 @@ export class Struct { 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 = ''; @@ -244,18 +240,6 @@ export class Struct { } out += '};\n'; - // Sanity Check Offsets - for (const property of this.#properties) { - 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; } @@ -266,7 +250,7 @@ export class Struct { // Static Properties for (const property of this.#staticProperties) { - out += `${property.referenceDefinition(true)} = *(${property.type()} *) ${toHex(property.offset)};\n`; + out += `${property.referenceDefinition(true)} = *(std::remove_reference_t *) ${toHex(property.offset)};\n`; } // Methods @@ -276,6 +260,18 @@ export class Struct { } } + // Sanity Check Offsets + for (const property of this.#properties) { + 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); + } + // VTable if (this.#vtable !== null) { const vtable = this.#vtable.generateCode(this.#directParent); diff --git a/src/test/parsing.ts b/src/test/parsing.ts index d798627..20a9a8f 100644 --- a/src/test/parsing.ts +++ b/src/test/parsing.ts @@ -1,6 +1,6 @@ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; -import { parseTypeAndName, prependArg, safeParseInt } from '../common'; +import { extractArrayInfo, forwardArguments, parseTypeAndName, prependArg, safeParseInt } from '../common'; import { parseMethod, parseProperty } from '../loader'; describe('Parsing Variable Declarations', () => { @@ -109,7 +109,7 @@ describe('Parsing Methods', () => { const method = parseMethod(test.input, test.self, !test.isStatic, false); assert.strictEqual(method.shortName, test.out.name); assert.strictEqual(method.returnType, test.out.returnType); - assert.strictEqual(method.args, test.out.args); + assert.strictEqual(method.getArgs(), test.out.args); assert.strictEqual(method.address, test.out.address); } else { assert.throws(() => { @@ -118,4 +118,49 @@ describe('Parsing Methods', () => { } }); } +}); + +describe('Extracting Array Information', () => { + const tests: { name: string, input: string, out: { name: string, arrayInfo: string } }[] = [ + {name: 'Basic', input: 'x', out: {name: 'x', arrayInfo: ''}}, + {name: 'Array', input: 'x[10]', out: {name: 'x', arrayInfo: '[10]'}}, + {name: 'Multi-Dimensional Array', input: 'x[10][10]', out: {name: 'x', arrayInfo: '[10][10]'}} + ]; + for (const test of tests) { + it(test.name, () => { + const out = extractArrayInfo(test.input); + assert.strictEqual(out.name, test.out.name); + assert.strictEqual(out.arrayInfo, test.out.arrayInfo); + }); + } +}); + +describe('Forwarding Arguments', () => { + const tests: { name: string, input: string, extra?: string[], out?: string }[] = [ + {name: 'No Arguments', input: '()', out: '()'}, + {name: 'One Argument', input: '(int x)', out: '(x)'}, + {name: 'Two Argument', input: '(int x, float y)', out: '(x, y)'}, + {name: 'Arrays', input: '(int one[10], float two[20])', out: '(one, two)'}, + {name: 'Generics #1', input: '(Obj x, Obj2 *y)', out: '(x, y)'}, + {name: 'Generics #2', input: '(function &callback, void *data)', out: '(callback, data)'}, + {name: 'Extra Arguments', input: '(int x)', extra: ['this'], out: '(this, x)'}, + {name: 'Malformed #1', input: '(int)'}, + {name: 'Malformed #2', input: '('}, + {name: 'Malformed #3', input: ')'}, + {name: 'Malformed #4', input: '(Obj { + if (test.out) { + const out = forwardArguments(test.input, test.extra ?? []); + assert.strictEqual(out, test.out); + } else { + assert.throws(() => { + forwardArguments(test.input, test.extra ?? []); + }); + } + }); + } }); \ No newline at end of file diff --git a/src/vtable.ts b/src/vtable.ts index ccf659e..94d784e 100644 --- a/src/vtable.ts +++ b/src/vtable.ts @@ -1,4 +1,4 @@ -import { BUILDING_SYMBOLS_GUARD, INDENT, POINTER_SIZE, assertSize, getSelfArg, preventConstruction, toHex } from './common'; +import { BUILDING_SYMBOLS_GUARD, INDENT, INTERNAL, POINTER_SIZE, assertSize, preventConstruction, toHex } from './common'; import { Method } from './method'; import { Property } from './property'; @@ -82,7 +82,7 @@ export class VTable { out.push(...this.#methods); // Add Destructors (https://stackoverflow.com/a/17960941) const destructor_return = `${this.#self} *`; - const destructor_args = `(${getSelfArg(this.#self)})`; + const destructor_args = '()'; if (this.#destructors.length === 0) { this.#destructors.push( new Method(this.#self, 'destructor_complete', destructor_return, destructor_args, 0x0 + this.#destructorOffset, false, false), @@ -149,7 +149,7 @@ export class VTable { if (info) { out += info.getProperty(); } else { - out += `${INDENT}void *unknown${i.toString()};\n`; + out += `${INDENT}void *${INTERNAL}unknown${i.toString()};\n`; } } if (this.#size === null) { @@ -162,11 +162,6 @@ export class VTable { } out += '};\n'; - // Sanity Check Size - if (this.#size !== null) { - out += assertSize(this.#getName(), this.#size); - } - // Return return out; } @@ -194,6 +189,11 @@ export class VTable { } } + // Sanity Check Size + if (this.#size !== null) { + out += assertSize(this.#getName(), this.#size); + } + // Return return out; }