t7x/deps/asmjit/tools/enumgen.js
2023-12-06 17:43:39 -05:00

418 lines
11 KiB
JavaScript

"use strict";
const fs = require("fs");
const path = require("path");
const hasOwn = Object.prototype.hasOwnProperty;
// ============================================================================
// [Tokenizer]
// ============================================================================
// The list of "token types" which our lexer understands:
const tokenizerPatterns = [
{ type: "space" , re: /^\s+/ },
{ type: "comment" , re: /^(\/\/.*(\n|$)|\/\*.*\*\/)/ },
{ type: "symbol" , re: /^[a-zA-Z_]\w*/ },
{ type: "integer" , re: /^(-?\d+|0[x|X][0-9A-Fa-f]+)(l)?(l)?(u)?\b/ },
{ type: "comma" , re: /^,/ },
{ type: "operator", re: /(\+|\+\+|-|--|\/|\*|<<|>>|=|==|<|<=|>|>=|&|&&|\||\|\||\^|~|!)/ },
{ type: "paren" , re: /^[\(\)\{\}\[\]]/ }
];
function nextToken(input, from, patterns) {
if (from >= input.length) {
return {
type: "end",
begin: from,
end: from,
content: ""
}
}
const s = input.slice(from);
for (var i = 0; i < patterns.length; i++) {
const pattern = patterns[i];
const result = s.match(pattern.re);
if (result !== null) {
const content = result[0];
return {
type: pattern.type,
begin: from,
end: from + content.length,
content: content
};
}
}
return {
type: "invalid",
begin: from,
end: from + 1,
content: input[from]
};
}
class Tokenizer {
constructor(input, patterns) {
this.input = input;
this.index = 0;
this.patterns = patterns;
}
next() {
for (;;) {
const token = nextToken(this.input, this.index, this.patterns);
this.index = token.end;
if (token.type === "space" || token.type === "comment")
continue;
return token;
}
}
revert(token) {
this.index = token.begin;
}
}
// ============================================================================
// [Parser]
// ============================================================================
function parseEnum(input) {
const map = Object.create(null);
const hasOwn = Object.prototype.hasOwnProperty;
const tokenizer = new Tokenizer(input, tokenizerPatterns);
var value = -1;
for (;;) {
var token = tokenizer.next();
if (token.type === "end")
break;
if (token.type === "symbol") {
const symbol = token.content;
token = tokenizer.next();
if (token.content === "=") {
token = tokenizer.next();
if (token.type !== "integer")
throw Error(`Expected an integer after symbol '${symbol} = '`);
value = parseInt(token.content);
}
else {
value++;
}
if (!hasOwn.call(map, symbol))
map[symbol] = value;
else
console.log(`${symbol} already defined, skipping...`);
token = tokenizer.next();
if (token.type !== "comma")
tokenizer.revert(token);
continue;
}
throw Error(`Unexpected token ${token.type} (${token.content})`);
}
return map;
}
// ============================================================================
// [Stringify]
// ============================================================================
function compare(a, b) {
return a < b ? -1 : a == b ? 0 : 1;
}
function compactedSize(table) {
var size = 0;
for (var i = 0; i < table.length; i++)
size += table[i].name.length + 1;
return size;
}
function indexTypeFromSize(size) {
if (size <= 256)
return 'uint8_t';
else if (size <= 65536)
return 'uint16_t';
else
return 'uint32_t';
}
function indent(s, indentation) {
var lines = s.split(/\r?\n/g);
if (indentation) {
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line) lines[i] = indentation + line;
}
}
return lines.join("\n");
}
function stringifyEnum(map, options) {
var output = "";
const stripPrefix = options.strip;
const outputPrefix = options.output;
var max = -1;
var table = [];
for (var k in map) {
var name = k;
if (stripPrefix) {
if (name.startsWith(stripPrefix))
name = name.substring(stripPrefix.length);
else
throw Error(`Cannot strip prefix '${stripPrefix}' in '${key}'`);
}
table.push({ name: name, value: map[k] });
max = Math.max(max, map[k]);
}
table.sort(function(a, b) { return compare(a.value, b.value); });
const unknownIndex = compactedSize(table);
table.push({ name: "<Unknown>", value: max + 1 });
const indexType = indexTypeFromSize(compactedSize(table));
function buildStringData() {
var s = "";
for (var i = 0; i < table.length; i++) {
s += ` "${table[i].name}\\0"`;
if (i == table.length - 1)
s += `;`;
s += `\n`;
}
return s;
}
function buildIndexData() {
var index = 0;
var indexArray = [];
for (var i = 0; i < table.length; i++) {
while (indexArray.length < table[i].value)
indexArray.push(unknownIndex);
indexArray.push(index);
index += table[i].name.length + 1;
}
var s = "";
var line = "";
var pos = 0;
for (var i = 0; i < indexArray.length; i++) {
if (line)
line += " ";
line += `${indexArray[i]}`;
if (i != indexArray.length - 1)
line += `,`;
if (i == indexArray.length - 1 || line.length >= 72) {
s += ` ${line}\n`;
line = "";
}
}
return s;
}
output += `static const char ${outputPrefix}String[] =\n` + buildStringData() + `\n`;
output += `static const ${indexType} ${outputPrefix}Index[] = {\n` + buildIndexData() + `};\n`;
return output;
}
// ============================================================================
// [FileSystem]
// ============================================================================
function walkDir(baseDir) {
function walk(baseDir, nestedPath, out) {
fs.readdirSync(baseDir).forEach((file) => {
const stat = fs.statSync(path.join(baseDir, file));
if (stat.isDirectory()) {
if (!stat.isSymbolicLink())
walk(path.join(baseDir, file), path.join(nestedPath, file), out)
}
else {
out.push(path.join(nestedPath, file));
}
});
return out;
}
return walk(baseDir, "", []);
}
// ============================================================================
// [Generator]
// ============================================================================
class Generator {
constructor(options) {
this.enumMap = Object.create(null);
this.outputs = [];
this.verify = options.verify;
this.baseDir = options.baseDir;
this.noBackup = options.noBackup;
}
readEnums() {
console.log(`Scanning: ${this.baseDir}`);
walkDir(this.baseDir).forEach((fileName) => {
if (/\.(cc|cpp|h|hpp)$/.test(fileName)) {
const content = fs.readFileSync(path.join(this.baseDir, fileName), "utf8");
this.addEnumsFromSource(fileName, content);
if (/@EnumStringBegin(\{.*\})@/.test(content))
this.outputs.push(fileName);
}
});
}
writeEnums() {
this.outputs.forEach((fileName) => {
console.log(`Output: ${fileName}`);
const oldContent = fs.readFileSync(path.join(this.baseDir, fileName), "utf8");
const newContent = this.injectEnumsToSource(oldContent);
if (oldContent != newContent) {
if (this.verify) {
console.log(` FAILED: File is not up to date.`);
process.exit(1);
}
else {
if (!this.noBackup) {
fs.writeFileSync(path.join(this.baseDir, fileName + ".backup"), oldContent, "utf8");
console.log(` Created ${fileName}.backup`);
}
fs.writeFileSync(path.join(this.baseDir, fileName), newContent, "utf8");
console.log(` Updated ${fileName}`);
}
}
else {
console.log(` File is up to date.`);
}
});
}
addEnumsFromSource(fileName, src) {
var found = false;
const matches = [...src.matchAll(/(?:@EnumValuesBegin(\{.*\})@|@EnumValuesEnd@)/g)];
for (var i = 0; i < matches.length; i += 2) {
const def = matches[i];
const end = matches[i + 1];
if (!def[0].startsWith("@EnumValuesBegin"))
throw new Error(`Cannot start with '${def[0]}'`);
if (!end)
throw new Error(`Missing @EnumValuesEnd for '${def[0]}'`);
if (!end[0].startsWith("@EnumValuesEnd@"))
throw new Error(`Expected @EnumValuesEnd@ for '${def[0]}' and not '${end[0]}'`);
const options = JSON.parse(def[1]);
const enumName = options.enum;
if (!enumName)
throw Error(`Missing 'enum' in '${def[0]}`);
if (hasOwn.call(this.enumMap, enumName))
throw new Error(`Enumeration '${enumName}' is already defined`);
const startIndex = src.lastIndexOf("\n", def.index) + 1;
const endIndex = end.index + end[0].length;
if (startIndex === -1 || startIndex > endIndex)
throw new Error(`Internal Error, indexes have unexpected values: startIndex=${startIndex} endIndex=${endIndex}`);
if (!found) {
found = true;
console.log(`Found: ${fileName}`);
}
console.log(` Parsing Enum: ${enumName}`);
this.enumMap[enumName] = parseEnum(src.substring(startIndex, endIndex));
}
}
injectEnumsToSource(src) {
const matches = [...src.matchAll(/(?:@EnumStringBegin(\{.*\})@|@EnumStringEnd@)/g)];
var delta = 0;
for (var i = 0; i < matches.length; i += 2) {
const def = matches[i];
const end = matches[i + 1];
if (!def[0].startsWith("@EnumStringBegin"))
throw new Error(`Cannot start with '${def[0]}'`);
if (!end)
throw new Error(`Missing @EnumStringEnd@ for '${def[0]}'`);
if (!end[0].startsWith("@EnumStringEnd@"))
throw new Error(`Expected @EnumStringEnd@ for '${def[0]}' and not '${end[0]}'`);
const options = JSON.parse(def[1]);
const enumName = options.enum;
if (!enumName)
throwError(`Missing 'name' in '${def[0]}`);
if (!hasOwn.call(this.enumMap, enumName))
throw new Error(`Enumeration '${enumName}' not found`);
console.log(` Injecting Enum: ${enumName}`);
const startIndex = src.indexOf("\n", def.index + delta) + 1;
const endIndex = src.lastIndexOf("\n", end.index + delta) + 1;
if (startIndex === -1 || endIndex === -1 || startIndex > endIndex)
throw new Error(`Internal Error, indexes have unexpected values: startIndex=${startIndex} endIndex=${endIndex}`);
// Calculate the indentation.
const indentation = (function() {
const begin = src.lastIndexOf("\n", def.index + delta) + 1;
const end = src.indexOf("/", begin);
return src.substring(begin, end);
})();
const newContent = indent(stringifyEnum(this.enumMap[enumName], options), indentation);
src = src.substring(0, startIndex) + newContent + src.substring(endIndex);
delta -= endIndex - startIndex;
delta += newContent.length;
}
return src;
}
}
const generator = new Generator({
baseDir : path.resolve(__dirname, "../src"),
verify : process.argv.indexOf("--verify") !== -1,
noBackup: process.argv.indexOf("--no-backup") !== -1
});
generator.readEnums();
generator.writeEnums();