diff options
Diffstat (limited to '3rdparty/asmjit/tools/enumgen.js')
-rw-r--r-- | 3rdparty/asmjit/tools/enumgen.js | 417 |
1 files changed, 417 insertions, 0 deletions
diff --git a/3rdparty/asmjit/tools/enumgen.js b/3rdparty/asmjit/tools/enumgen.js new file mode 100644 index 00000000000..b9d57e3dac9 --- /dev/null +++ b/3rdparty/asmjit/tools/enumgen.js @@ -0,0 +1,417 @@ +"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(); |