Improve Tests
All checks were successful
CI / Build (push) Successful in 15s

This commit is contained in:
TheBrokenRail 2025-02-24 06:25:48 -05:00
parent 8dc7b17251
commit 4d64fd2190
8 changed files with 180 additions and 74 deletions

View File

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

View File

@ -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 += '_';

View File

@ -1,5 +1,6 @@
import { INTERNAL, formatType } from './common';
// A Single Property
export class Property {
readonly offset: number;
readonly #type: string;

View File

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

View File

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

View File

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