Simplify Generated Code
All checks were successful
CI / Build (push) Successful in 17s

This commit is contained in:
TheBrokenRail 2025-03-30 22:49:51 -04:00
parent 3db1515ecf
commit 818b7535eb
11 changed files with 192 additions and 71 deletions

View File

@ -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

View File

@ -7,4 +7,8 @@
__PREVENT_DESTRUCTION(self)
#define __PREVENT_COPY(self) \
self(const self &) = delete; \
self &operator=(const self &) = delete
self &operator=(const self &) = delete
template <typename T>
static constexpr decltype(sizeof(T)) __real_sizeof() {
return sizeof(std::conditional_t<std::is_reference_v<T>, void *, T>);
}

View File

@ -63,4 +63,8 @@ public:
};
#undef super
// Disable Warnings
#pragma GCC diagnostic ignored "-Wparentheses"
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
{{ main }}

View File

@ -5,6 +5,9 @@
#error "Symbols Are ARM-Only"
#endif
// Type Traits
#include <type_traits>
// 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 }}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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}`;
}
}

View File

@ -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<decltype(args)>(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<decltype(${property.prettyName()})> *) ${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);

View File

@ -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<float> x, Obj2<int, int[10]> *y)', out: '(x, y)'},
{name: 'Generics #2', input: '(function<int(int, int)> &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<float x, int y)'},
{name: 'Malformed #5', input: '(int x, float y[5)'},
{name: 'Malformed #6', input: '(Obj<5] x)'}
];
for (const test of tests) {
it(test.name, () => {
if (test.out) {
const out = forwardArguments(test.input, test.extra ?? []);
assert.strictEqual(out, test.out);
} else {
assert.throws(() => {
forwardArguments(test.input, test.extra ?? []);
});
}
});
}
});

View File

@ -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;
}