Initial Prototype
This commit is contained in:
commit
e99239fecf
2
.eslintignore
Normal file
2
.eslintignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/build
|
||||||
|
/node_modules
|
36
.eslintrc
Normal file
36
.eslintrc
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"plugins": [
|
||||||
|
"@typescript-eslint"
|
||||||
|
],
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/eslint-recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"no-constant-condition": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"checkLoops": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quotes": [
|
||||||
|
"error",
|
||||||
|
"single",
|
||||||
|
{
|
||||||
|
"allowTemplateLiterals": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"semi": "error",
|
||||||
|
"indent": [
|
||||||
|
"error",
|
||||||
|
4,
|
||||||
|
{
|
||||||
|
"SwitchCase": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"eqeqeq": "error"
|
||||||
|
}
|
||||||
|
}
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/build
|
||||||
|
/package-lock.json
|
||||||
|
/node_modules
|
||||||
|
/out
|
||||||
|
/symbols
|
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"cSpell.words": [
|
||||||
|
"vtable"
|
||||||
|
]
|
||||||
|
}
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 TheBrokenRail
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
22
package.json
Normal file
22
package.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "symbol-processor",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "build/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "tsc && node build/index.js",
|
||||||
|
"lint": "eslint . --ext .ts"
|
||||||
|
},
|
||||||
|
"author": "TheBrokenRail",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@tsconfig/node-lts": "^18.12.4",
|
||||||
|
"@tsconfig/strictest": "^2.0.1",
|
||||||
|
"@types/node": "^20.5.6",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.4.1",
|
||||||
|
"@typescript-eslint/parser": "^6.4.1",
|
||||||
|
"eslint": "^8.48.0",
|
||||||
|
"openapi-types": "^12.1.3",
|
||||||
|
"typescript": "^5.2.2"
|
||||||
|
}
|
||||||
|
}
|
49
src/common.ts
Normal file
49
src/common.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import * as fs from 'node:fs';
|
||||||
|
|
||||||
|
export const INDENT = ' ';
|
||||||
|
export const POINTER_SIZE = 4;
|
||||||
|
export const EXTENSION = '.def';
|
||||||
|
export function readDefinition(name: string) {
|
||||||
|
const file = `${__dirname}/../symbols/${name}${EXTENSION}`;
|
||||||
|
return fs.readFileSync(file, {encoding: 'utf8'});
|
||||||
|
}
|
||||||
|
export function parseTypeAndName(parts: string[]) {
|
||||||
|
let type = parts[0]!;
|
||||||
|
let name = parts[1]!;
|
||||||
|
const index = name.lastIndexOf('*');
|
||||||
|
if (index !== -1) {
|
||||||
|
type += ' ' + name.substring(0, index + 1);
|
||||||
|
name = name.substring(index + 1);
|
||||||
|
}
|
||||||
|
return {type, name};
|
||||||
|
}
|
||||||
|
export const MIN_SIZE = 1;
|
||||||
|
export function toUpperSnakeCase(str: string) {
|
||||||
|
let wasUpper = false;
|
||||||
|
let nextIsUpper = false;
|
||||||
|
let out = '';
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
let character = str.charAt(i);
|
||||||
|
if (character === '_') {
|
||||||
|
wasUpper = false;
|
||||||
|
nextIsUpper = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const isUpper = character === character.toUpperCase() || nextIsUpper;
|
||||||
|
nextIsUpper = false;
|
||||||
|
character = character.toUpperCase();
|
||||||
|
if (isUpper && i > 0 && !wasUpper) {
|
||||||
|
out += '_';
|
||||||
|
}
|
||||||
|
out += character;
|
||||||
|
wasUpper = isUpper;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
export function formatType(type: string) {
|
||||||
|
if (!type.endsWith('*')) {
|
||||||
|
type += ' ';
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
export const COMMENT = '//';
|
176
src/index.ts
Normal file
176
src/index.ts
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
import * as fs from 'node:fs';
|
||||||
|
import { EXTENSION, INDENT } from './common';
|
||||||
|
import { getStructure, setCppAllowed } from './map';
|
||||||
|
|
||||||
|
// Output Directory
|
||||||
|
let outputDirectory = process.argv[2];
|
||||||
|
if (!outputDirectory) {
|
||||||
|
outputDirectory = `${__dirname}/../out`;
|
||||||
|
}
|
||||||
|
fs.rmSync(outputDirectory, {force: true, recursive: true});
|
||||||
|
fs.mkdirSync(outputDirectory, {recursive: true});
|
||||||
|
|
||||||
|
// Main
|
||||||
|
function loadSymbols() {
|
||||||
|
// Load
|
||||||
|
const files = fs.readdirSync(`${__dirname}/../symbols`);
|
||||||
|
const structureObjects = [];
|
||||||
|
for (const file of files) {
|
||||||
|
if (file.endsWith(EXTENSION)) {
|
||||||
|
// Get Name
|
||||||
|
const parts = file.split('/');
|
||||||
|
let name = parts[parts.length - 1]!;
|
||||||
|
name = name.substring(0, name.length - EXTENSION.length);
|
||||||
|
// Structures
|
||||||
|
structureObjects.push(getStructure(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort
|
||||||
|
structureObjects.sort((a, b) => {
|
||||||
|
if (a.getName() > b.getName()) {
|
||||||
|
return 1;
|
||||||
|
} else if (a.getName() < b.getName()) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Return
|
||||||
|
return structureObjects;
|
||||||
|
}
|
||||||
|
function makeHeader(output: string, allowCpp: boolean) {
|
||||||
|
// Set Mode
|
||||||
|
setCppAllowed(allowCpp);
|
||||||
|
|
||||||
|
// Load Symbols
|
||||||
|
const structureObjects = loadSymbols();
|
||||||
|
|
||||||
|
// Forward Declarations
|
||||||
|
let forwardDeclarations = '// Forward Declarations\n';
|
||||||
|
for (const structure of structureObjects) {
|
||||||
|
const name = structure.getName();
|
||||||
|
forwardDeclarations += `typedef struct ${name} ${name};\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort Structures By Dependency
|
||||||
|
while (true) {
|
||||||
|
let valid = true;
|
||||||
|
// Loop Through Structures
|
||||||
|
for (const structure of structureObjects) {
|
||||||
|
// Loop Through Dependencies
|
||||||
|
const currentIndex = structureObjects.indexOf(structure);
|
||||||
|
for (const dependency of structure.getDependencies()) {
|
||||||
|
// Compare Current And Dependency Index
|
||||||
|
const obj = getStructure(dependency);
|
||||||
|
const dependencyIndex = structureObjects.indexOf(obj);
|
||||||
|
if (dependencyIndex > currentIndex) {
|
||||||
|
// Dependency Must Be Moved
|
||||||
|
structureObjects.splice(dependencyIndex, 1);
|
||||||
|
structureObjects.splice(currentIndex, 0, obj);
|
||||||
|
valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check If Any Changes Were Made
|
||||||
|
if (!valid) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check If Any Changes Were Made
|
||||||
|
if (valid) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Code
|
||||||
|
let structures = '';
|
||||||
|
for (const structure of structureObjects) {
|
||||||
|
const name = structure.getName();
|
||||||
|
structures += `\n// ${name}\n`;
|
||||||
|
try {
|
||||||
|
structures += structure.generate();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Error Generating Header: ${name}: ${e instanceof Error ? e.stack : e}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write
|
||||||
|
let result = '';
|
||||||
|
result += '#pragma once\n';
|
||||||
|
result += '\n';
|
||||||
|
result += forwardDeclarations;
|
||||||
|
result += '\n// Init\n';
|
||||||
|
result += 'void init_symbols();';
|
||||||
|
result += '\n// Extra Definitions\n';
|
||||||
|
result += fs.readFileSync(`${__dirname}/../symbols/extra.h`, {encoding: 'utf8'}).trim() + '\n';
|
||||||
|
result += structures;
|
||||||
|
fs.writeFileSync(output, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run
|
||||||
|
makeHeader(`${outputDirectory}/minecraft_c.h`, false);
|
||||||
|
makeHeader(`${outputDirectory}/minecraft_cpp.h`, true);
|
||||||
|
|
||||||
|
// Generate Compiled Code
|
||||||
|
function makeCompiledCode(output: string) {
|
||||||
|
// Set Mode
|
||||||
|
setCppAllowed(true);
|
||||||
|
// Load Symbols
|
||||||
|
const structureObjects = loadSymbols();
|
||||||
|
|
||||||
|
// Generate
|
||||||
|
let declarations = '';
|
||||||
|
let init = '';
|
||||||
|
for (const structure of structureObjects) {
|
||||||
|
const name = structure.getName();
|
||||||
|
declarations += `\n// ${name}\n`;
|
||||||
|
init += `\n${INDENT}// ${name}\n`;
|
||||||
|
try {
|
||||||
|
const code = structure.generateCode();
|
||||||
|
declarations += code.functions;
|
||||||
|
init += code.init;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Error Generating Code: ${name}: ${e instanceof Error ? e.stack : e}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write
|
||||||
|
let result = '';
|
||||||
|
result += '#include "minecraft.h"\n';
|
||||||
|
result += '\n// Init\n';
|
||||||
|
result += 'void init_symbols() {';
|
||||||
|
result += init;
|
||||||
|
result += '}\n';
|
||||||
|
result += declarations;
|
||||||
|
fs.writeFileSync(output, result);
|
||||||
|
}
|
||||||
|
makeCompiledCode(`${outputDirectory}/minecraft.cpp`);
|
||||||
|
|
||||||
|
// Create Main Header
|
||||||
|
function makeMainHeader(output: string) {
|
||||||
|
let result = '';
|
||||||
|
result += '#pragma once\n';
|
||||||
|
result += '#pragma GCC diagnostic push\n';
|
||||||
|
result += '#pragma GCC diagnostic ignored "-Wunused-variable"\n';
|
||||||
|
result += '#pragma GCC diagnostic ignored "-Wunused-function"\n';
|
||||||
|
result += 'typedef unsigned char uchar;\n';
|
||||||
|
result += 'typedef unsigned int uint;\n';
|
||||||
|
result += '#include <string.h>\n';
|
||||||
|
result += '#include <stdlib.h>\n';
|
||||||
|
result += '#include <stddef.h>\n';
|
||||||
|
result += '#ifndef __cplusplus\n';
|
||||||
|
result += 'typedef unsigned char bool;\n';
|
||||||
|
result += '#include "minecraft_c.h"\n';
|
||||||
|
result += '#else\n';
|
||||||
|
result += '#include <string>\n';
|
||||||
|
result += '#include <vector>\n';
|
||||||
|
result += 'extern "C" {\n';
|
||||||
|
result += '#include "minecraft_cpp.h"\n';
|
||||||
|
result += '}\n';
|
||||||
|
result += '#endif\n';
|
||||||
|
result += '#pragma GCC diagnostic pop\n';
|
||||||
|
fs.writeFileSync(output, result);
|
||||||
|
}
|
||||||
|
makeMainHeader(`${outputDirectory}/minecraft.h`);
|
229
src/loader.ts
Normal file
229
src/loader.ts
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
import { COMMENT, EXTENSION, parseTypeAndName, readDefinition } from './common';
|
||||||
|
import { isCppAllowed } from './map';
|
||||||
|
import { Method } from './method';
|
||||||
|
import { SimpleProperty, StaticProperty } from './property';
|
||||||
|
import { Struct } from './struct';
|
||||||
|
|
||||||
|
function safeParseInt(str: string) {
|
||||||
|
const x = parseInt(str);
|
||||||
|
if (isNaN(x)) {
|
||||||
|
throw new Error('Invalid Integer: ' + str);
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
function syntaxError(message?: string) {
|
||||||
|
throw new Error('Syntax Error' + (message ? `: ${message}` : ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ErrorOnLine {
|
||||||
|
readonly error: unknown;
|
||||||
|
readonly file: string;
|
||||||
|
readonly line: number;
|
||||||
|
constructor (error: unknown, file: string, line: number) {
|
||||||
|
this.error = error;
|
||||||
|
this.file = file;
|
||||||
|
this.line = line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse Property
|
||||||
|
function parseProperty(args: string) {
|
||||||
|
const parts = args.split(' ');
|
||||||
|
if (parts.length !== 4) {
|
||||||
|
syntaxError('Invalid Piece Count');
|
||||||
|
}
|
||||||
|
if (parts[2] !== '=') {
|
||||||
|
syntaxError();
|
||||||
|
}
|
||||||
|
const {type, name} = parseTypeAndName([parts[0]!, parts[1]!]);
|
||||||
|
const offset = safeParseInt(parts[3]!);
|
||||||
|
return {type, name, offset};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse Method
|
||||||
|
function parseMethod(args: string, self: string, insertSelfArg: boolean) {
|
||||||
|
const argsStart = args.indexOf(' (');
|
||||||
|
if (argsStart === -1) {
|
||||||
|
syntaxError('Cannot Find Arguments');
|
||||||
|
}
|
||||||
|
const start = args.substring(0, argsStart).split(' ');
|
||||||
|
if (start.length !== 2) {
|
||||||
|
syntaxError('Invalid Piece Count');
|
||||||
|
}
|
||||||
|
const {type, name} = parseTypeAndName([start[0]!, start[1]!]);
|
||||||
|
const end = args.substring(argsStart + 1).split(' = ');
|
||||||
|
if (end.length !== 2) {
|
||||||
|
syntaxError('Invalid Piece Count');
|
||||||
|
}
|
||||||
|
let methodArgs = end[0]!;
|
||||||
|
if (!methodArgs.startsWith('(') || !methodArgs.endsWith(')')) {
|
||||||
|
syntaxError('Invalid Method Arguments');
|
||||||
|
}
|
||||||
|
if (insertSelfArg) {
|
||||||
|
let selfArg = `(${self} *self`;
|
||||||
|
if (methodArgs !== '()') {
|
||||||
|
selfArg += ', ';
|
||||||
|
}
|
||||||
|
methodArgs = selfArg + methodArgs.substring(1);
|
||||||
|
}
|
||||||
|
const address = safeParseInt(end[1]!);
|
||||||
|
return new Method(self, name, type, methodArgs, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load Structure
|
||||||
|
export function load(target: Struct, name: string, isExtended: boolean) {
|
||||||
|
// Read File
|
||||||
|
let data = readDefinition(name);
|
||||||
|
|
||||||
|
// Strip Comments
|
||||||
|
const lines = [];
|
||||||
|
for (let line of data.split('\n')) {
|
||||||
|
// Trim
|
||||||
|
line = line.trim();
|
||||||
|
// Remove Comments
|
||||||
|
const index = line.indexOf(COMMENT);
|
||||||
|
if (index !== -1) {
|
||||||
|
line = line.substring(0, index);
|
||||||
|
}
|
||||||
|
// Store Line
|
||||||
|
lines.push(line);
|
||||||
|
}
|
||||||
|
data = lines.join('\n');
|
||||||
|
|
||||||
|
// Line-By-Line
|
||||||
|
let cursor = 0;
|
||||||
|
for (let piece of data.split(';')) {
|
||||||
|
// Find Start Of Command For Error Handling
|
||||||
|
const startOfCommand = cursor + piece.search(/\S|$/);
|
||||||
|
// Advance Cursor
|
||||||
|
cursor += piece.length + 1;
|
||||||
|
// Handle Errors
|
||||||
|
try {
|
||||||
|
// Trim
|
||||||
|
piece = piece.trim();
|
||||||
|
// Replace Newlines With Spaces
|
||||||
|
piece = piece.replace(/\n/g, ' ');
|
||||||
|
// Simplify
|
||||||
|
piece = piece.replace(/\s+/g, ' ');
|
||||||
|
// Skip Empty Piece
|
||||||
|
if (piece.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Skip C++ Types If Applicable
|
||||||
|
if (!isCppAllowed() && piece.includes('std::')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Commands
|
||||||
|
let firstSpace = piece.indexOf(' ');
|
||||||
|
if (firstSpace === -1) {
|
||||||
|
firstSpace = piece.length;
|
||||||
|
}
|
||||||
|
const command = piece.substring(0, firstSpace);
|
||||||
|
const args = piece.substring(firstSpace + 1);
|
||||||
|
switch (command) {
|
||||||
|
case 'extends': {
|
||||||
|
// Load Parent
|
||||||
|
load(target, args, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'size': {
|
||||||
|
// Set Size
|
||||||
|
if (!isExtended) {
|
||||||
|
target.setSize(safeParseInt(args));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'vtable-size': {
|
||||||
|
// Set VTable Size
|
||||||
|
if (!isExtended) {
|
||||||
|
target.setVTableSize(safeParseInt(args));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'vtable': {
|
||||||
|
// Set VTable Address
|
||||||
|
if (!isExtended) {
|
||||||
|
target.setVTableAddress(safeParseInt(args));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'property': {
|
||||||
|
// Add Property
|
||||||
|
const info = parseProperty(args);
|
||||||
|
target.addProperty(new SimpleProperty(info.offset, info.type, info.name));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'static-property': {
|
||||||
|
// Add Static Property
|
||||||
|
if (!isExtended) {
|
||||||
|
const info = parseProperty(args);
|
||||||
|
target.addStaticProperty(new StaticProperty(info.offset, info.type, info.name, target.getName()));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'method': {
|
||||||
|
// Add Method
|
||||||
|
const method = parseMethod(args, target.getName(), true);
|
||||||
|
target.addMethod(method, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'virtual-method': {
|
||||||
|
// Add Virtual Method
|
||||||
|
const method = parseMethod(args, target.getName(), true);
|
||||||
|
target.addMethod(method, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'static-method': {
|
||||||
|
// Add Static Method
|
||||||
|
if (!isExtended) {
|
||||||
|
const method = parseMethod(args, target.getName(), false);
|
||||||
|
target.addMethod(method, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'constructor': {
|
||||||
|
// Constructor
|
||||||
|
if (!isExtended) {
|
||||||
|
let data = `${target.getName()} *constructor`;
|
||||||
|
if (args.startsWith('(')) {
|
||||||
|
// No Custom Name
|
||||||
|
data += ' ';
|
||||||
|
} else {
|
||||||
|
// Use Custom Name
|
||||||
|
data += '_';
|
||||||
|
}
|
||||||
|
data += args;
|
||||||
|
const method = parseMethod(data, target.getName(), true);
|
||||||
|
target.addMethod(method, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'requires': {
|
||||||
|
// Add Dependency
|
||||||
|
if (args.length === 0) {
|
||||||
|
syntaxError('Missing Dependency Name');
|
||||||
|
}
|
||||||
|
target.addDependency(args);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error(`Invalid Command: ${command}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof ErrorOnLine) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
// Find Line Number
|
||||||
|
let lineNumber = 1;
|
||||||
|
for (let i = 0; i <= startOfCommand; i++) {
|
||||||
|
if (data.charAt(i) === '\n') {
|
||||||
|
lineNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Rethrow Error With Line Number
|
||||||
|
throw new ErrorOnLine(e, `${name}${EXTENSION}`, lineNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
src/map.ts
Normal file
52
src/map.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { Struct } from './struct';
|
||||||
|
import { ErrorOnLine, load } from './loader';
|
||||||
|
|
||||||
|
// Track Mode
|
||||||
|
let allowCpp = false;
|
||||||
|
export function isCppAllowed() {
|
||||||
|
return allowCpp;
|
||||||
|
}
|
||||||
|
export function setCppAllowed(newMode: boolean) {
|
||||||
|
clearStructures();
|
||||||
|
allowCpp = newMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store Loaded Structures
|
||||||
|
const structures: {[id: string]: Struct} = {};
|
||||||
|
|
||||||
|
// Get Or Load Structure
|
||||||
|
export function getStructure(name: string) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(structures, name)) {
|
||||||
|
// Already Loaded
|
||||||
|
return structures[name]!;
|
||||||
|
} else {
|
||||||
|
// Load Structure
|
||||||
|
try {
|
||||||
|
// Create Structure
|
||||||
|
const target = new Struct(name);
|
||||||
|
structures[name] = target;
|
||||||
|
// Parse File
|
||||||
|
load(target, name, false);
|
||||||
|
// Return
|
||||||
|
return target;
|
||||||
|
} catch (e) {
|
||||||
|
let error = e;
|
||||||
|
let extra = '';
|
||||||
|
if (e instanceof ErrorOnLine) {
|
||||||
|
extra = `${e.file}:${e.line}: `;
|
||||||
|
error = e.error;
|
||||||
|
}
|
||||||
|
console.log(`Error Loading ${name}: ${extra}${error instanceof Error ? error.stack : error}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear Loaded Structures
|
||||||
|
function clearStructures() {
|
||||||
|
for (const name in structures) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(structures, name)) {
|
||||||
|
delete structures[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
src/method.ts
Normal file
37
src/method.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { formatType } from './common';
|
||||||
|
|
||||||
|
export class Method {
|
||||||
|
readonly self: string;
|
||||||
|
readonly shortName: string;
|
||||||
|
readonly returnType: string;
|
||||||
|
readonly args: string;
|
||||||
|
readonly address: number;
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
constructor(self: string, name: string, returnType: string, args: string, address: number) {
|
||||||
|
this.self = self;
|
||||||
|
this.shortName = name;
|
||||||
|
this.returnType = returnType;
|
||||||
|
this.args = args;
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Type
|
||||||
|
getName() {
|
||||||
|
return `${this.self}_${this.shortName}`;
|
||||||
|
}
|
||||||
|
getType() {
|
||||||
|
return `${this.getName()}_t`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Type Definition
|
||||||
|
generateTypedef() {
|
||||||
|
const returnType = formatType(this.returnType);
|
||||||
|
return `typedef ${returnType}(*${this.getType()})${this.args};\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Variable Definition
|
||||||
|
generateDefinition() {
|
||||||
|
return `${this.getType()} ${this.getName()};\n`;
|
||||||
|
}
|
||||||
|
}
|
129
src/property.ts
Normal file
129
src/property.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import { POINTER_SIZE } from './common';
|
||||||
|
import { getStructure } from './map';
|
||||||
|
|
||||||
|
export interface Property {
|
||||||
|
propertyOffset(): number;
|
||||||
|
propertySize(): number;
|
||||||
|
propertyType(): string;
|
||||||
|
propertyName(): string;
|
||||||
|
propertyAlignment(): number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SimpleProperty implements Property {
|
||||||
|
readonly #offset: number;
|
||||||
|
readonly #type: string;
|
||||||
|
readonly #name: string;
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
constructor(offset: number, type: string, name: string) {
|
||||||
|
this.#offset = offset;
|
||||||
|
this.#type = type;
|
||||||
|
this.#name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
propertyOffset() {
|
||||||
|
return this.#offset;
|
||||||
|
}
|
||||||
|
propertyType() {
|
||||||
|
return this.#type;
|
||||||
|
}
|
||||||
|
propertyName() {
|
||||||
|
return this.#name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size
|
||||||
|
propertySize() {
|
||||||
|
if (this.#type.endsWith('*')) {
|
||||||
|
// Pointer
|
||||||
|
return POINTER_SIZE;
|
||||||
|
} else if (this.#type === 'int' || this.#type === 'uint') {
|
||||||
|
// Integer
|
||||||
|
return 4;
|
||||||
|
} else if (this.#type === 'float') {
|
||||||
|
// Float
|
||||||
|
return 4;
|
||||||
|
} else if (this.#type === 'bool') {
|
||||||
|
// Boolean
|
||||||
|
return 1;
|
||||||
|
} else if (this.#type === 'std::string') {
|
||||||
|
// C++ String
|
||||||
|
return 4;
|
||||||
|
} else if (this.#type === 'char' || this.#type === 'uchar') {
|
||||||
|
// Character
|
||||||
|
return 1;
|
||||||
|
} else if (this.#type.startsWith('std::vector<')) {
|
||||||
|
// C++ Vector
|
||||||
|
return 12;
|
||||||
|
} else {
|
||||||
|
// Structure
|
||||||
|
const structure = getStructure(this.#type);
|
||||||
|
return structure.getSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alignment
|
||||||
|
propertyAlignment() {
|
||||||
|
if (this.#type.endsWith('*')) {
|
||||||
|
// Pointer
|
||||||
|
return POINTER_SIZE;
|
||||||
|
} else if (this.#type === 'int' || this.#type === 'uint') {
|
||||||
|
// Integer
|
||||||
|
return 4;
|
||||||
|
} else if (this.#type === 'float') {
|
||||||
|
// Float
|
||||||
|
return 4;
|
||||||
|
} else if (this.#type === 'bool') {
|
||||||
|
// Boolean
|
||||||
|
return 1;
|
||||||
|
} else if (this.#type === 'std::string') {
|
||||||
|
// C++ String
|
||||||
|
return 4;
|
||||||
|
} else if (this.#type === 'char' || this.#type === 'uchar') {
|
||||||
|
// Character
|
||||||
|
return 1;
|
||||||
|
} else if (this.#type.startsWith('std::vector<')) {
|
||||||
|
// C++ Vector
|
||||||
|
return 4;
|
||||||
|
} else {
|
||||||
|
// Structure
|
||||||
|
const structure = getStructure(this.#type);
|
||||||
|
return structure.getAlignment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StaticProperty {
|
||||||
|
readonly address: number;
|
||||||
|
readonly #type: string;
|
||||||
|
readonly #name: string;
|
||||||
|
readonly #self: string;
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
constructor(address: number, type: string, name: string, self: string) {
|
||||||
|
this.address = address;
|
||||||
|
this.#type = type;
|
||||||
|
this.#name = name;
|
||||||
|
this.#self = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name And Type
|
||||||
|
getName() {
|
||||||
|
return `${this.#self}_${this.#name}`;
|
||||||
|
}
|
||||||
|
getType() {
|
||||||
|
let type = this.#type;
|
||||||
|
// Convert To Pointer
|
||||||
|
if (!type.endsWith('*')) {
|
||||||
|
type += ' ';
|
||||||
|
}
|
||||||
|
type += '*';
|
||||||
|
// Return
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Variable Definition
|
||||||
|
generateDefinition() {
|
||||||
|
return `${this.getType()}${this.getName()};\n`;
|
||||||
|
}
|
||||||
|
}
|
277
src/struct.ts
Normal file
277
src/struct.ts
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
import { INDENT, MIN_SIZE, toUpperSnakeCase, formatType } from './common';
|
||||||
|
import { isCppAllowed } from './map';
|
||||||
|
import { Method } from './method';
|
||||||
|
import { Property, StaticProperty } from './property';
|
||||||
|
import { VTable } from './vtable';
|
||||||
|
|
||||||
|
export class Struct {
|
||||||
|
readonly #name: string;
|
||||||
|
#vtable: VTable | null;
|
||||||
|
readonly #methods: Method[];
|
||||||
|
readonly #properties: Property[];
|
||||||
|
#size: number | null;
|
||||||
|
readonly #dependencies: string[];
|
||||||
|
readonly #staticProperties: StaticProperty[];
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
constructor(name: string) {
|
||||||
|
this.#name = name;
|
||||||
|
this.#methods = [];
|
||||||
|
this.#properties = [];
|
||||||
|
this.#vtable = null;
|
||||||
|
this.#size = null;
|
||||||
|
this.#dependencies = [];
|
||||||
|
this.#staticProperties = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dependencies
|
||||||
|
addDependency(dependency: string) {
|
||||||
|
this.#dependencies.push(dependency);
|
||||||
|
}
|
||||||
|
getDependencies() {
|
||||||
|
return this.#dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure VTable Exists
|
||||||
|
#ensureVTable() {
|
||||||
|
if (this.#vtable === null) {
|
||||||
|
this.#vtable = new VTable(this.#name);
|
||||||
|
this.addProperty(this.#vtable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setters
|
||||||
|
setSize(size: number) {
|
||||||
|
this.#size = size;
|
||||||
|
}
|
||||||
|
// Getters
|
||||||
|
#roundSize(size: number) {
|
||||||
|
const alignment = this.getAlignment();
|
||||||
|
return Math.ceil(size / alignment) * alignment;
|
||||||
|
}
|
||||||
|
getSize() {
|
||||||
|
let size;
|
||||||
|
if (this.#size !== null) {
|
||||||
|
size = this.#size;
|
||||||
|
} else {
|
||||||
|
size = MIN_SIZE;
|
||||||
|
for (const property of this.#properties) {
|
||||||
|
const newSize = property.propertyOffset() + property.propertySize();
|
||||||
|
if (newSize > size) {
|
||||||
|
size = newSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size = this.#roundSize(size);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
getName() {
|
||||||
|
return this.#name;
|
||||||
|
}
|
||||||
|
getAlignment() {
|
||||||
|
let alignment = 1;
|
||||||
|
for (const property of this.#properties) {
|
||||||
|
const x = property.propertyAlignment();
|
||||||
|
if (x > alignment) {
|
||||||
|
alignment = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return alignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Method
|
||||||
|
addMethod(method: Method, isVirtual: boolean) {
|
||||||
|
if (method.self !== this.#name) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
if (isVirtual) {
|
||||||
|
this.#ensureVTable();
|
||||||
|
this.#vtable!.add(method);
|
||||||
|
} else {
|
||||||
|
this.#methods.push(method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add Property
|
||||||
|
addProperty(property: Property) {
|
||||||
|
this.#properties.push(property);
|
||||||
|
}
|
||||||
|
// Add Static Property
|
||||||
|
addStaticProperty(property: StaticProperty) {
|
||||||
|
this.#staticProperties.push(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure VTable
|
||||||
|
setVTableSize(size: number) {
|
||||||
|
this.#ensureVTable();
|
||||||
|
this.#vtable!.setSize(size);
|
||||||
|
}
|
||||||
|
setVTableAddress(address: number) {
|
||||||
|
this.#ensureVTable();
|
||||||
|
this.#vtable!.setAddress(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check
|
||||||
|
#check() {
|
||||||
|
// Sort Properties
|
||||||
|
this.#properties.sort((a, b) => a.propertyOffset() - b.propertyOffset());
|
||||||
|
|
||||||
|
// Check Size
|
||||||
|
const size = this.getSize();
|
||||||
|
if (this.#size !== null) {
|
||||||
|
// Check Alignment
|
||||||
|
if (size !== this.#size) {
|
||||||
|
throw new Error('Size Misaligned');
|
||||||
|
}
|
||||||
|
// Check If Size Is Too Small
|
||||||
|
const lastProperty = this.#properties[this.#properties.length - 1];
|
||||||
|
if (lastProperty) {
|
||||||
|
let realSize = lastProperty.propertyOffset() + lastProperty.propertySize();
|
||||||
|
realSize = this.#roundSize(realSize);
|
||||||
|
if (realSize > this.#size) {
|
||||||
|
throw new Error(`Structure Size Too Small: ${this.#size}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Header
|
||||||
|
generate() {
|
||||||
|
let out = '';
|
||||||
|
|
||||||
|
// Check
|
||||||
|
this.#check();
|
||||||
|
|
||||||
|
// Static Properties
|
||||||
|
for (const property of this.#staticProperties) {
|
||||||
|
out += `extern ${property.generateDefinition()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
for (const method of this.#methods) {
|
||||||
|
out += method.generateTypedef();
|
||||||
|
out += `extern ${method.generateDefinition()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// VTable
|
||||||
|
if (this.#vtable !== null) {
|
||||||
|
out += this.#vtable.generate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Structure
|
||||||
|
out += `struct ${this.#name} {\n`;
|
||||||
|
for (let i = 0; i <= this.#properties.length; i++) {
|
||||||
|
const property = this.#properties[i];
|
||||||
|
|
||||||
|
// Padding
|
||||||
|
const lastProperty = this.#properties[i - 1];
|
||||||
|
let neededPadding = 0;
|
||||||
|
if (i === 0) {
|
||||||
|
// Start Of Structure Padding
|
||||||
|
if (property) {
|
||||||
|
neededPadding = property.propertyOffset();
|
||||||
|
} else if (this.#properties.length === 0) {
|
||||||
|
if (this.#size !== null) {
|
||||||
|
neededPadding = this.#size;
|
||||||
|
} else {
|
||||||
|
neededPadding = MIN_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (i === this.#properties.length) {
|
||||||
|
// End Of Structure Padding
|
||||||
|
if (this.#size !== null && lastProperty) {
|
||||||
|
const realSize = lastProperty.propertyOffset() + lastProperty.propertySize();
|
||||||
|
neededPadding = this.#size - realSize;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Inner Structure Padding
|
||||||
|
if (property && lastProperty) {
|
||||||
|
const realSize = lastProperty.propertyOffset() + lastProperty.propertySize();
|
||||||
|
neededPadding = property.propertyOffset() - realSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (neededPadding > 0) {
|
||||||
|
out += `${INDENT}uchar padding${i}[${neededPadding}];\n`;
|
||||||
|
} else if (neededPadding < 0) {
|
||||||
|
throw new Error('Overlapping properties detected!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Property
|
||||||
|
if (property) {
|
||||||
|
// Check Offset
|
||||||
|
const offset = property.propertyOffset();
|
||||||
|
const alignment = property.propertyAlignment();
|
||||||
|
if ((offset % alignment) !== 0) {
|
||||||
|
throw new Error('Misaligned Property Offset');
|
||||||
|
}
|
||||||
|
// Add
|
||||||
|
const type = formatType(property.propertyType());
|
||||||
|
out += `${INDENT}${type}${property.propertyName()};\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out += `};\n`;
|
||||||
|
|
||||||
|
// Sanity Check Offsets
|
||||||
|
const assertFunction = isCppAllowed() ? 'static_assert' : '_Static_assert';
|
||||||
|
for (let i = 0; i < this.#properties.length; i++) {
|
||||||
|
const property = this.#properties[i]!;
|
||||||
|
const name = property.propertyName();
|
||||||
|
const offset = property.propertyOffset();
|
||||||
|
out += `${assertFunction}(offsetof(${this.#name}, ${name}) == ${offset}, "Invalid Offset");\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity Check Size
|
||||||
|
const size = this.getSize();
|
||||||
|
out += `#define ${toUpperSnakeCase(this.#name)}_SIZE ${size}\n`;
|
||||||
|
out += `${assertFunction}(sizeof (${this.#name}) == ${toUpperSnakeCase(this.#name)}_SIZE, "Invalid Structure Size");\n`;
|
||||||
|
if (this.#size === null) {
|
||||||
|
// Hide Structure Size As The Real Size Is Unknown
|
||||||
|
out += `#undef ${toUpperSnakeCase(this.#name)}_SIZE\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocation Function
|
||||||
|
if (this.#size !== null) {
|
||||||
|
out += `${this.#name} *alloc_${this.#name}();\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Compiled Code
|
||||||
|
generateCode() {
|
||||||
|
let declarations = '';
|
||||||
|
let init = '';
|
||||||
|
|
||||||
|
// Check
|
||||||
|
this.#check();
|
||||||
|
|
||||||
|
// Static Properties
|
||||||
|
for (const property of this.#staticProperties) {
|
||||||
|
init += `${INDENT}${property.getName()} = (${property.getType()}) ${property.address};\n`;
|
||||||
|
declarations += property.generateDefinition();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
for (const method of this.#methods) {
|
||||||
|
init += `${INDENT}${method.getName()} = (${method.getType()}) ${method.address};\n`;
|
||||||
|
declarations += method.generateDefinition();
|
||||||
|
}
|
||||||
|
|
||||||
|
// VTable
|
||||||
|
if (this.#vtable !== null) {
|
||||||
|
const vtable = this.#vtable.generateCode();
|
||||||
|
declarations += vtable.declarations;
|
||||||
|
init += vtable.init;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocation Function
|
||||||
|
if (this.#size !== null) {
|
||||||
|
declarations += `${this.#name} *alloc_${this.#name}() {\n`;
|
||||||
|
declarations += `${INDENT}return (${this.#name} *) ::operator new(${this.#size});\n`;
|
||||||
|
declarations += '}\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return {functions: declarations, init};
|
||||||
|
}
|
||||||
|
}
|
176
src/vtable.ts
Normal file
176
src/vtable.ts
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
import { INDENT, POINTER_SIZE } from './common';
|
||||||
|
import { Method } from './method';
|
||||||
|
import { Property } from './property';
|
||||||
|
|
||||||
|
export class VTable implements Property {
|
||||||
|
readonly #name: string;
|
||||||
|
#address: number | null;
|
||||||
|
#size: number | null;
|
||||||
|
readonly #methods: Method[];
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
constructor(name: string) {
|
||||||
|
this.#name = name;
|
||||||
|
this.#address = null;
|
||||||
|
this.#size = null;
|
||||||
|
this.#methods = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Property Information
|
||||||
|
propertyOffset() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
propertySize() {
|
||||||
|
return POINTER_SIZE;
|
||||||
|
}
|
||||||
|
propertyType() {
|
||||||
|
return this.#getName() + ' *';
|
||||||
|
}
|
||||||
|
propertyName() {
|
||||||
|
return 'vtable';
|
||||||
|
}
|
||||||
|
propertyAlignment() {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setters
|
||||||
|
setAddress(address: number) {
|
||||||
|
this.#address = address;
|
||||||
|
}
|
||||||
|
setSize(size: number) {
|
||||||
|
this.#size = size;
|
||||||
|
if ((this.#size % POINTER_SIZE) !== 0) {
|
||||||
|
throw new Error(`Invalid VTable Size: ${this.#size}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add To VTable
|
||||||
|
add(method: Method) {
|
||||||
|
// Check Offset
|
||||||
|
const offset = method.address;
|
||||||
|
if ((offset % POINTER_SIZE) !== 0) {
|
||||||
|
throw new Error(`Invalid VTable Offset: ${offset}`);
|
||||||
|
}
|
||||||
|
// Check Size
|
||||||
|
if (this.#size !== null && (offset + POINTER_SIZE) > this.#size) {
|
||||||
|
throw new Error(`VTable Offset Too Large: ${offset}`);
|
||||||
|
}
|
||||||
|
// Add
|
||||||
|
const index = offset / POINTER_SIZE;
|
||||||
|
this.#methods[index] = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Structure Name
|
||||||
|
#getName() {
|
||||||
|
return this.#name + '_vtable';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check
|
||||||
|
#check() {
|
||||||
|
// Check Size
|
||||||
|
if (this.#size !== null) {
|
||||||
|
const maxMethodCount = this.#size / POINTER_SIZE;
|
||||||
|
if (maxMethodCount < this.#methods.length) {
|
||||||
|
throw new Error(`VTable Size Too Small: ${this.#size}`);
|
||||||
|
}
|
||||||
|
this.#methods.length = maxMethodCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Code
|
||||||
|
generate() {
|
||||||
|
let out = '';
|
||||||
|
|
||||||
|
// Check
|
||||||
|
this.#check();
|
||||||
|
|
||||||
|
// Method Prototypes
|
||||||
|
for (let i = 0; i < this.#methods.length; i++) {
|
||||||
|
const info = this.#methods[i];
|
||||||
|
if (info) {
|
||||||
|
out += info.generateTypedef();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Structure
|
||||||
|
out += `typedef struct ${this.#getName()} ${this.#getName()};\n`;
|
||||||
|
out += `struct ${this.#getName()} {\n`;
|
||||||
|
for (let i = 0; i < this.#methods.length; i++) {
|
||||||
|
let name = `unknown${i}`;
|
||||||
|
let type = 'void *';
|
||||||
|
const info = this.#methods[i];
|
||||||
|
if (info) {
|
||||||
|
name = info.shortName;
|
||||||
|
type = info.getType() + ' ';
|
||||||
|
}
|
||||||
|
out += `${INDENT}${type}${name};\n`;
|
||||||
|
}
|
||||||
|
out += `};\n`;
|
||||||
|
|
||||||
|
// Pointers
|
||||||
|
if (this.#address !== null) {
|
||||||
|
// Base
|
||||||
|
out += `extern ${this.#getName()} *${this.#getName()}_base;\n`;
|
||||||
|
// Methods
|
||||||
|
for (let i = 0; i < this.#methods.length; i++) {
|
||||||
|
const info = this.#methods[i];
|
||||||
|
if (info) {
|
||||||
|
const type = `${info.getType()} *`;
|
||||||
|
out += `extern ${type}${info.getName()}_vtable_addr;\n`;
|
||||||
|
out += `extern ${info.generateDefinition()}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duplication Method
|
||||||
|
if (this.#size !== null) {
|
||||||
|
out += `${this.#getName()} *dup_${this.#getName()}(${this.#getName()} *vtable);\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Compiled Code
|
||||||
|
generateCode() {
|
||||||
|
let declarations = '';
|
||||||
|
let init = '';
|
||||||
|
|
||||||
|
// Check
|
||||||
|
this.#check();
|
||||||
|
|
||||||
|
// Pointers
|
||||||
|
if (this.#address !== null) {
|
||||||
|
// Base
|
||||||
|
init += `${INDENT}${this.#getName()}_base = (${this.#getName()} *) ${this.#address};\n`;
|
||||||
|
declarations += `${this.#getName()} *${this.#getName()}_base;\n`;
|
||||||
|
// Methods
|
||||||
|
for (let i = 0; i < this.#methods.length; i++) {
|
||||||
|
const info = this.#methods[i];
|
||||||
|
if (info) {
|
||||||
|
const vtableAddress = this.#address + (i * POINTER_SIZE);
|
||||||
|
const type = `${info.getType()} *`;
|
||||||
|
init += `${INDENT}${info.getName()}_vtable_addr = (${type}) ${vtableAddress};\n`;
|
||||||
|
declarations += `${type}${info.getName()}_vtable_addr;\n`;
|
||||||
|
init += `${INDENT}${info.getName()} = *${info.getName()}_vtable_addr;\n`;
|
||||||
|
declarations += info.generateDefinition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duplication Method
|
||||||
|
if (this.#size !== null) {
|
||||||
|
declarations += `${this.#getName()} *dup_${this.#getName()}(${this.#getName()} *vtable) {\n`;
|
||||||
|
declarations += `${INDENT}${this.#getName()} *obj = (${this.#getName()} *) malloc(${this.#size});\n`;
|
||||||
|
declarations += `${INDENT}if (obj == NULL) {\n`;
|
||||||
|
declarations += `${INDENT}${INDENT}return NULL;\n`;
|
||||||
|
declarations += `${INDENT}}\n`;
|
||||||
|
declarations += `${INDENT}memcpy((void *) obj, (void *) vtable, ${this.#size});\n`;
|
||||||
|
declarations += `${INDENT}return obj;\n`;
|
||||||
|
declarations += '}\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return {declarations, init};
|
||||||
|
}
|
||||||
|
}
|
13
tsconfig.json
Normal file
13
tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"@tsconfig/strictest/tsconfig",
|
||||||
|
"@tsconfig/node-lts/tsconfig"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "./src",
|
||||||
|
"outDir": "./build"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user