This commit is contained in:
parent
3db1515ecf
commit
818b7535eb
@ -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
|
||||
|
@ -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>);
|
||||
}
|
@ -63,4 +63,8 @@ public:
|
||||
};
|
||||
#undef super
|
||||
|
||||
// Disable Warnings
|
||||
#pragma GCC diagnostic ignored "-Wparentheses"
|
||||
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
|
||||
|
||||
{{ main }}
|
@ -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 }}
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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}`;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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 ?? []);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user