This commit is contained in:
parent
8dc7b17251
commit
4d64fd2190
@ -36,6 +36,7 @@ export function syntaxError(message?: string): never {
|
||||
// Convert 'int x' Into {type: 'int', name: 'x'}
|
||||
const POINTER_TOKENS = ['*', '&'];
|
||||
export function parseTypeAndName(piece: string) {
|
||||
piece = piece.trim();
|
||||
// Split On Last Space
|
||||
const index = piece.lastIndexOf(' ');
|
||||
if (index === -1) {
|
||||
@ -52,6 +53,9 @@ export function parseTypeAndName(piece: string) {
|
||||
}
|
||||
type += x;
|
||||
}
|
||||
// Trim
|
||||
name = name.trim();
|
||||
type = type.trim();
|
||||
// Return
|
||||
return {type, name};
|
||||
}
|
||||
@ -95,6 +99,7 @@ export function assertSize(name: string, size: number) {
|
||||
}
|
||||
// Parse Integer With Error Checking
|
||||
export function safeParseInt(str: string) {
|
||||
str = str.trim();
|
||||
const x = parseInt(str);
|
||||
if (isNaN(x)) {
|
||||
throw new Error('Invalid Integer: ' + str);
|
||||
|
@ -4,7 +4,7 @@ import { Property, StaticProperty } from './property';
|
||||
import { Struct } from './struct';
|
||||
|
||||
// Error Handling
|
||||
export class ErrorOnLine extends Error {
|
||||
class ErrorOnLine extends Error {
|
||||
constructor (e: unknown, file: string, line: number) {
|
||||
// Get Message
|
||||
let message: string;
|
||||
@ -20,8 +20,9 @@ export class ErrorOnLine extends Error {
|
||||
}
|
||||
|
||||
// Parse Property
|
||||
function parseProperty(args: string) {
|
||||
const [a, b] = args.split(' = ');
|
||||
export const OFFSET_SEPARATOR = ' = ';
|
||||
export function parseProperty(args: string) {
|
||||
const [a, b] = args.split(OFFSET_SEPARATOR);
|
||||
if (!a || !b) {
|
||||
syntaxError('Invalid Piece Count');
|
||||
}
|
||||
@ -31,18 +32,18 @@ function parseProperty(args: string) {
|
||||
}
|
||||
|
||||
// Parse Method
|
||||
function parseMethod(args: string, self: string, insertSelfArg: boolean, isInherited: boolean) {
|
||||
export function parseMethod(args: string, self: string, insertSelfArg: boolean, isInherited: boolean) {
|
||||
const argsStart = args.indexOf('(');
|
||||
if (argsStart === -1) {
|
||||
syntaxError('Cannot Find Arguments');
|
||||
}
|
||||
const start = args.substring(0, argsStart).trim();
|
||||
const {type, name} = parseTypeAndName(start);
|
||||
const end = args.substring(argsStart).trim().split(' = ');
|
||||
const end = args.substring(argsStart).trim().split(OFFSET_SEPARATOR);
|
||||
if (!end[0] || !end[1]) {
|
||||
syntaxError('Invalid Piece Count');
|
||||
}
|
||||
let methodArgs = end[0];
|
||||
let methodArgs = end[0].trim();
|
||||
if (!methodArgs.startsWith('(') || !methodArgs.endsWith(')')) {
|
||||
syntaxError('Invalid Method Arguments');
|
||||
}
|
||||
@ -172,7 +173,6 @@ export function load(target: Struct, name: string, isExtended: boolean) {
|
||||
let data = `${target.name} *constructor`;
|
||||
if (args.startsWith('(')) {
|
||||
// No Custom Name
|
||||
data += ' ';
|
||||
} else {
|
||||
// Use Custom Name
|
||||
data += '_';
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { INTERNAL, formatType } from './common';
|
||||
|
||||
// A Single Property
|
||||
export class Property {
|
||||
readonly offset: number;
|
||||
readonly #type: string;
|
||||
|
@ -3,6 +3,7 @@ import { Method } from './method';
|
||||
import { Property, StaticProperty } from './property';
|
||||
import { VTable } from './vtable';
|
||||
|
||||
// A Structure/Class
|
||||
export class Struct {
|
||||
readonly name: string;
|
||||
#vtable: VTable | null;
|
||||
|
@ -1,67 +0,0 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { formatType, parseTypeAndName, prependArg, toUpperSnakeCase } from '../common';
|
||||
|
||||
describe('Parsing Variable Declarations', () => {
|
||||
const tests: [string, string, string, string][] = [
|
||||
['Simple', 'int x', 'int', 'x'],
|
||||
['Pointer', 'int *y', 'int *', 'y'],
|
||||
['Ugly Pointer', 'int* z', 'int*', 'z'],
|
||||
['Double Pointer', 'int **a', 'int **', 'a'],
|
||||
['Ugly Double Pointer', 'int* *b', 'int**', 'b'],
|
||||
['Reference', 'int &c', 'int &', 'c'],
|
||||
['Reference-To-Pointer', 'int *&d', 'int *&', 'd']
|
||||
];
|
||||
for (const test of tests) {
|
||||
it(test[0], () => {
|
||||
const obj = parseTypeAndName(test[1]);
|
||||
assert.strictEqual(obj.type, test[2]);
|
||||
assert.strictEqual(obj.name, test[3]);
|
||||
});
|
||||
}
|
||||
it('Invalid', () => {
|
||||
assert.throws(() => {
|
||||
parseTypeAndName('abc');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Upper-Snake-Case', () => {
|
||||
const tests: [string, string, string][] = [
|
||||
['One Word', 'Hello', 'HELLO'],
|
||||
['Two Words', 'HelloWorld', 'HELLO_WORLD'],
|
||||
['Empty', '', ''],
|
||||
['All Uppercase', 'HELLO', 'HELLO'],
|
||||
['All Lowercase', 'hello', 'HELLO']
|
||||
];
|
||||
for (const test of tests) {
|
||||
it(test[0], () => {
|
||||
assert.strictEqual(toUpperSnakeCase(test[1]), test[2]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('Formatting Types For Concatenation', () => {
|
||||
const tests: [string, string, string][] = [
|
||||
['Value', 'int', 'int '],
|
||||
['Pointer', 'int *', 'int *'],
|
||||
['Reference', 'int &', 'int &']
|
||||
];
|
||||
for (const test of tests) {
|
||||
it(test[0], () => {
|
||||
assert.strictEqual(formatType(test[1]), test[2]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('Prepending Arguments To Functions', () => {
|
||||
const tests: [string, string, string, string][] = [
|
||||
['Empty', '()', 'int x', '(int x)'],
|
||||
['Non-Empty', '(int x)', 'int y', '(int y, int x)']
|
||||
];
|
||||
for (const test of tests) {
|
||||
it(test[0], () => {
|
||||
assert.strictEqual(prependArg(test[1], test[2]), test[3]);
|
||||
});
|
||||
}
|
||||
});
|
44
src/test/formatting.ts
Normal file
44
src/test/formatting.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { formatType, toHex, toUpperSnakeCase } from '../common';
|
||||
|
||||
describe('Upper-Snake-Case', () => {
|
||||
const tests: [string, string, string][] = [
|
||||
['One Word', 'Hello', 'HELLO'],
|
||||
['Two Words', 'HelloWorld', 'HELLO_WORLD'],
|
||||
['Empty', '', ''],
|
||||
['All Uppercase', 'HELLO', 'HELLO'],
|
||||
['All Lowercase', 'hello', 'HELLO']
|
||||
];
|
||||
for (const test of tests) {
|
||||
it(test[0], () => {
|
||||
assert.strictEqual(toUpperSnakeCase(test[1]), test[2]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('Formatting Types For Concatenation', () => {
|
||||
const tests: [string, string, string][] = [
|
||||
['Value', 'int', 'int '],
|
||||
['Pointer', 'int *', 'int *'],
|
||||
['Reference', 'int &', 'int &']
|
||||
];
|
||||
for (const test of tests) {
|
||||
it(test[0], () => {
|
||||
assert.strictEqual(formatType(test[1]), test[2]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('Hexadecimal', () => {
|
||||
const tests: [string, number, string][] = [
|
||||
['Zero', 0, '0x0'],
|
||||
['Small Number', 10, '0xa'],
|
||||
['Large Number', 100, '0x64']
|
||||
];
|
||||
for (const test of tests) {
|
||||
it(test[0], () => {
|
||||
assert.strictEqual(toHex(test[1]), test[2]);
|
||||
});
|
||||
}
|
||||
});
|
121
src/test/parsing.ts
Normal file
121
src/test/parsing.ts
Normal file
@ -0,0 +1,121 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { parseTypeAndName, prependArg, safeParseInt } from '../common';
|
||||
import { parseMethod, parseProperty } from '../loader';
|
||||
|
||||
describe('Parsing Variable Declarations', () => {
|
||||
const tests: {name: string, input: string, out: {type: string, name: string}}[] = [
|
||||
{name: 'Simple', input: 'int x', out: {type: 'int', name: 'x'}},
|
||||
{name: 'Pointer', input: 'int *y', out: {type: 'int *', name: 'y'}},
|
||||
{name: 'Extra Space', input: ' float a ', out: {type: 'float', name: 'a'}},
|
||||
{name: 'Ugly Pointer', input: 'int* z', out: {type: 'int*', name: 'z'}},
|
||||
{name: 'Double Pointer', input: 'int **a', out: {type: 'int **', name: 'a'}},
|
||||
{name: 'Ugly Double Pointer', input: 'int* *b', out: {type: 'int**', name: 'b'}},
|
||||
{name: 'Reference', input: 'int &c', out: {type: 'int &', name: 'c'}},
|
||||
{name: 'Reference-To-Pointer', input: 'int *&d', out: {type: 'int *&', name: 'd'}}
|
||||
];
|
||||
for (const test of tests) {
|
||||
it(test.name, () => {
|
||||
const obj = parseTypeAndName(test.input);
|
||||
assert.strictEqual(obj.type, test.out.type);
|
||||
assert.strictEqual(obj.name, test.out.name);
|
||||
});
|
||||
}
|
||||
it('Invalid', () => {
|
||||
assert.throws(() => {
|
||||
parseTypeAndName('abc');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Prepending Arguments To Functions', () => {
|
||||
const tests: {name: string, input: string, arg: string, out: string}[] = [
|
||||
{name: 'Empty', input: '()', arg: 'int x', out: '(int x)'},
|
||||
{name: 'Non-Empty', input: '(int x)', arg: 'int y', out: '(int y, int x)'}
|
||||
];
|
||||
for (const test of tests) {
|
||||
it(test.name, () => {
|
||||
assert.strictEqual(prependArg(test.input, test.arg), test.out);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('Parsing Integers', () => {
|
||||
const tests: {name: string, input: string, out?: number}[] = [
|
||||
{name: 'Zero', input: '0', out: 0},
|
||||
{name: 'Negative', input: '-5', out: -5},
|
||||
{name: 'Positive', input: '5', out: 5},
|
||||
{name: 'Extra Space', input: ' 5 ', out: 5},
|
||||
{name: 'Hexadecimal', input: '0x10', out: 16},
|
||||
{name: 'Empty', input: ''},
|
||||
{name: 'Text', input: 'abc'}
|
||||
];
|
||||
for (const test of tests) {
|
||||
it(test.name, () => {
|
||||
if (test.out !== undefined) {
|
||||
assert.strictEqual(safeParseInt(test.input), test.out);
|
||||
} else {
|
||||
assert.throws(() => {
|
||||
safeParseInt(test.input);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('Parsing Properties', () => {
|
||||
const tests: {name: string, input: string, out?: {type: string, name: string, offset: number}}[] = [
|
||||
{name: 'Basic', input: 'int x = 0x0', out: {type: 'int', name: 'x', offset: 0}},
|
||||
{name: 'Extra Space', input: 'int z = 0x0', out: {type: 'int', name: 'z', offset: 0}},
|
||||
{name: 'Pointer', input: 'float *y = 0x4', out: {type: 'float *', name: 'y', offset: 4}},
|
||||
{name: 'Empty', input: ''},
|
||||
{name: 'Malformed #1', input: 'int x'},
|
||||
{name: 'Malformed #2', input: 'int x=2'},
|
||||
{name: 'Malformed #3', input: 'int x = '}
|
||||
];
|
||||
for (const test of tests) {
|
||||
it(test.name, () => {
|
||||
if (test.out) {
|
||||
const property = parseProperty(test.input);
|
||||
assert.strictEqual(property.type, test.out.type);
|
||||
assert.strictEqual(property.name, test.out.name);
|
||||
assert.strictEqual(property.offset, test.out.offset);
|
||||
} else {
|
||||
assert.throws(() => {
|
||||
parseProperty(test.input);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('Parsing Methods', () => {
|
||||
const tests: {name: string, input: string, self: string, isStatic: boolean, out?: {name: string, returnType: string, args: string, address: number}}[] = [
|
||||
{name: 'Basic', input: 'void func() = 0x10', self: 'Test', isStatic: false, out: {name: 'func', returnType: 'void', args: '(Test *self)', address: 16}},
|
||||
{name: 'Advanced', input: 'int bar(int x, float y, std::vector<float> *arr) = 0x20', self: 'Foo', isStatic: false, out: {name: 'bar', returnType: 'int', args: '(Foo *self, int x, float y, std::vector<float> *arr)', address: 32}},
|
||||
{name: 'Extra Space', input: 'int bar (int x) = 0x20', self: 'Foo', isStatic: false, out: {name: 'bar', returnType: 'int', args: '(Foo *self, int x)', address: 32}},
|
||||
{name: 'Static', input: 'int thing(float x) = 0x30', self: 'Crazy', isStatic: true, out: {name: 'thing', returnType: 'int', args: '(float x)', address: 48}},
|
||||
{name: 'Empty', input: '', self: 'Test', isStatic: false},
|
||||
{name: 'Malformed #1', input: 'int broken', self: 'Test', isStatic: false},
|
||||
{name: 'Malformed #2', input: 'int broken = 0x10', self: 'Test', isStatic: false},
|
||||
{name: 'Malformed #3', input: 'int broken()', self: 'Test', isStatic: false},
|
||||
{name: 'Malformed #4', input: 'int broken()=0x0', self: 'Test', isStatic: false},
|
||||
{name: 'Malformed #5', input: 'int broken() = ', self: 'Test', isStatic: false},
|
||||
{name: 'Malformed #6', input: 'abc', self: 'Test', isStatic: false}
|
||||
];
|
||||
for (const test of tests) {
|
||||
it(test.name, () => {
|
||||
if (test.out) {
|
||||
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.address, test.out.address);
|
||||
} else {
|
||||
assert.throws(() => {
|
||||
parseMethod(test.input, test.self, !test.isStatic, false);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
@ -2,6 +2,7 @@ import { BUILDING_SYMBOLS_GUARD, INDENT, POINTER_SIZE, assertSize, getSelfArg, p
|
||||
import { Method } from './method';
|
||||
import { Property } from './property';
|
||||
|
||||
// A VTable
|
||||
const structuresWithVTableAddress: string[] = [];
|
||||
export class VTable {
|
||||
readonly #self: string;
|
||||
|
Loading…
x
Reference in New Issue
Block a user