symbol-processor/src/common.ts

193 lines
5.4 KiB
TypeScript

import * as fs from 'node:fs';
export const INDENT = ' ';
export const POINTER_SIZE = 4;
export const RTTI_SIZE = POINTER_SIZE;
export const EXTENSION = '.def';
export const STRUCTURE_FILES: {[id: string]: string} = {};
export function readDefinition(name: string) {
if (!STRUCTURE_FILES[name]) {
throw new Error(`Missing Definition File: ${name}${EXTENSION}`);
}
return fs.readFileSync(STRUCTURE_FILES[name]!, {encoding: 'utf8'});
}
export function syntaxError(message?: string) {
throw new Error('Syntax Error' + (message ? `: ${message}` : ''));
}
export function parseTypeAndName(piece: string) {
// Split On Last Space
const index = piece.lastIndexOf(' ');
if (index === -1) {
syntaxError('Unable To Find Name/Type Divider');
}
let name = piece.substring(index + 1)!;
let type = piece.substring(0, index)!;
// Move Asterisks From Name To Type
while (name.startsWith('*')) {
name = name.substring(1);
if (!type.endsWith('*')) {
type += ' ';
}
type += '*';
}
// Return
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 = '//';
export function toHex(x: number) {
return '0x' + x.toString(16);
}
export function getAssertFunction() {
return 'static_assert';
}
export function assertSize(name: string, size: number, isDefined: boolean) {
let out = '';
// Define Size Macro
const macro = toUpperSnakeCase(name) + '_SIZE';
out += `#define ${macro} ${toHex(size)}\n`;
// Check Size
out += `${getAssertFunction()}(sizeof(${name}) == ${macro}, "Invalid Size");\n`;
// Hide Structure Size If The Real Size Is Unknown
if (!isDefined) {
out += `#undef ${macro}\n`;
}
// Return
return out;
}
export function safeParseInt(str: string) {
const x = parseInt(str);
if (isNaN(x)) {
throw new Error('Invalid Integer: ' + str);
}
return x;
}
export function stripArrayData(propertyName: string) {
const index = propertyName.indexOf('[');
if (index !== -1) {
propertyName = propertyName.substring(0, index);
}
return propertyName;
}
export function getSizeMultiplierFromArrayData(propertyName: string) {
let multiplier = 1;
// Check If Array Data Is Present
const index = propertyName.indexOf('[');
if (index === -1) {
return multiplier;
}
propertyName = propertyName.substring(index + 1);
// Strip Last ]
if (!propertyName.endsWith(']')) {
syntaxError('Expecting ]');
} else {
propertyName = propertyName.substring(0, propertyName.length - 1);
}
// Split
const parts = propertyName.split('][');
// Calculate Multiplier
for (const part of parts) {
multiplier *= safeParseInt(part);
}
// Return
return multiplier;
}
export function getSelfArg(type: string) {
return `${type} *self`;
}
export class Size {
#size: number | null;
#isExact: boolean;
readonly #pointerAligned: boolean;
constructor(pointerAligned: boolean) {
this.#size = null;
this.#isExact = false;
this.#pointerAligned = pointerAligned;
}
has() {
return this.#size !== null;
}
get() {
if (this.#size === null) {
throw new Error('No Size Specified');
}
return this.#size;
}
set(size: number, isExact: boolean) {
if (size < MIN_SIZE) {
throw new Error(`Size Too Small: ${toHex(size)}`);
}
if (this.#isExact) {
throw new Error('Cannot Change Size After Being Set By Base Structure');
}
if (this.#size !== null && size < this.#size) {
throw new Error('Cannot Decrease VTable Size');
}
this.#size = size;
this.#isExact = isExact;
if (this.#pointerAligned && (this.#size % POINTER_SIZE) !== 0) {
throw new Error(`Invalid Size: ${toHex(this.#size)}`);
}
}
// Whether This Is An Exact Limit Or Just A Lower Bound
isExact() {
return this.#isExact;
}
}
export function getArgNames(args: string) {
// Remove Parentheses
args = args.substring(1, args.length - 1);
if (args.length === 0) {
return [];
}
// Split
const argsList = args.split(',');
// Parse
const out = [];
for (let arg of argsList) {
arg = arg.trim();
// Remove Type
const nameStart = Math.max(arg.lastIndexOf(' '), arg.lastIndexOf('*')) + 1;
arg = arg.substring(nameStart);
// Collect
out.push(arg);
}
// Return
return out;
}
export function prependArg(args: string, arg: string) {
if (args !== '()') {
arg += ', ';
}
return '(' + arg + args.substring(1);
}