diff --git a/src/common.ts b/src/common.ts index 8ab0e69..d089432 100644 --- a/src/common.ts +++ b/src/common.ts @@ -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); diff --git a/src/loader.ts b/src/loader.ts index bf10e21..8a99a85 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -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 += '_'; diff --git a/src/property.ts b/src/property.ts index 8c3d154..c422a66 100644 --- a/src/property.ts +++ b/src/property.ts @@ -1,5 +1,6 @@ import { INTERNAL, formatType } from './common'; +// A Single Property export class Property { readonly offset: number; readonly #type: string; diff --git a/src/struct.ts b/src/struct.ts index a4047bb..b9c9ecf 100644 --- a/src/struct.ts +++ b/src/struct.ts @@ -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; diff --git a/src/test/common.ts b/src/test/common.ts deleted file mode 100644 index d870920..0000000 --- a/src/test/common.ts +++ /dev/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]); - }); - } -}); \ No newline at end of file diff --git a/src/test/formatting.ts b/src/test/formatting.ts new file mode 100644 index 0000000..6998b0c --- /dev/null +++ b/src/test/formatting.ts @@ -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]); + }); + } +}); \ No newline at end of file diff --git a/src/test/parsing.ts b/src/test/parsing.ts new file mode 100644 index 0000000..a13eadd --- /dev/null +++ b/src/test/parsing.ts @@ -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 *arr) = 0x20', self: 'Foo', isStatic: false, out: {name: 'bar', returnType: 'int', args: '(Foo *self, int x, float y, std::vector *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); + }); + } + }); + } +}); \ No newline at end of file diff --git a/src/vtable.ts b/src/vtable.ts index 89b8e2b..2f1b9e9 100644 --- a/src/vtable.ts +++ b/src/vtable.ts @@ -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;