raylib-wasm-transpiler/tools/js-class-c-struct-transpiler/transpile.js

284 lines
7.0 KiB
JavaScript

import { parseArgs } from "util";
let types = {
u8: {
c: "unsigned char",
js: "Uint8",
sql: "INTEGER",
size: 1,
},
i8: {
c: "char",
js: "Int8",
sql: "INTEGER",
size: 1,
},
u16: {
c: "unsigned short",
js: "Uint16",
sql: "INTEGER",
size: 2,
},
i16: {
c: "short",
js: "Int16",
sql: "INTEGER",
size: 2,
},
u32: {
c: "unisgned int",
js: "Uint32",
sql: "INTEGER",
size: 4,
},
i32: {
c: "int",
js: "Int32",
sql: "INTEGER",
size: 4,
},
i64: {
c: "long",
js: "BigInt64",
sql: "INTEGER"
},
u64: {
c: "unsigned long",
js: "BigUint64",
sql: "INTEGER"
},
f32: {
c: "float",
js: "Float32",
sql: "REAL",
size: 4,
},
f64: {
c: "double",
js: "Float64",
sql: "REAL",
size: 8,
},
};
const { values } = parseArgs({
args: Bun.argv,
options: {
schema: {
type: "string",
short: "S",
},
javascript_out: {
type: "string",
short: "j",
},
c_out: {
type: "string",
short: "c",
},
sqlite_out: {
type: "string",
short: "s"
}
},
strict: true,
allowPositionals: true,
});
function jsStructConstructor(size, containsString, type, args) {
return `
/**
* Constructs a new ${type}
*
* @param ${args} init The arguments to construct the object.
* @param {Uint8Array} ptr The pointer to the C struct.
*/
constructor(init = {}, ptr = undefined) {
this._size = ${size};
this._ptr = ptr || new Uint8Array(this._size);
this._data = new DataView(this._ptr.buffer);
${
containsString
? `
this._encoder = new TextEncoder();
this._decoder = new TextDecoder();`
: ""
}
for (const key of Object.keys(init)) {
this[key] = init[key];
}
}`;
}
const sFile = Bun.file(values.schema);
const schema = await sFile.json();
let cData = "";
let sqlData = "";
for (const type of Object.keys(schema)) {
let containsString = false;
let offset = 0;
let size = 0;
let importStatements = "";
let jsData = "";
let foreignKeys = "";
const props = schema[type].members;
let typeDef = `/**\n * @typedef {Object} ${type} ${schema[type].comment}\n`;
let args = `{{`;
sqlData += `CREATE TABLE ${type} (id INTEGER PRIMARY KEY AUTOINCREMENT`;
cData += `typedef struct ${type} {`;
jsData += `class ${type} {`;
for (const prop of Object.keys(props)) {
const propType = props[prop].type;
const kind = props[prop].kind;
const comment = props[prop].comment;
let typeSize = parseInt(types[propType]?.size);
switch (kind) {
case "logical":
typeSize = 1;
jsData += `
/**
* ${comment}
* @return {boolean} gets the value of ${prop}
*/
get ${prop}() {
return Boolean(this._data.getInt8(${parseInt(offset)}, true));
}
/**
* ${comment}
* @param {boolean} sets the value of ${prop}
*/
set ${prop}(v) {
return this._data.setInt8(${parseInt(
offset
)}, v ? 1 : 0, true);
}`;
args += `${prop}: boolean, `;
typeDef += ` * @property {boolean} ${propType} ${comment}\n`;
sqlData += `, ${prop} INTEGER`;
cData += `
char ${prop}; // ${comment}`;
break;
case "string":
containsString = true;
typeSize = props[prop].size;
const iSize = parseInt(offset) + parseInt(typeSize);
jsData += `
/**
* ${comment}
* @return {string} gets the value of ${prop}
*/
get ${prop}() {
return this._decoder.decode(new Uint8Array(this._ptr.slice(${parseInt(
offset
)}, ${iSize})));
}
/**
* ${comment}
* @param {string} v sets the value of ${prop}
*/
set ${prop}(v) {
if (v.length > ${parseInt(typeSize)}) {
throw new Error("input is larger than buffer size of ${parseInt(typeSize)}");
}
const tmp = new Uint8Array(new ArrayBuffer(${parseInt(typeSize)}));
tmp.set(this._encoder.encode(v))
this._ptr.set(tmp.buffer, ${parseInt(offset)});
}`;
sqlData += `, ${prop} TEXT`;
cData += `
char ${prop}[${iSize}]; // ${comment}`;
args += `${prop}: string, `;
typeDef += ` * @property {string} ${prop} ${comment}\n`;
break;
case "scalar":
typeSize = types[propType].size;
jsData += `
/**
* ${comment}
* @return {${types[propType].js}} gets the value of ${prop}
*/
get ${prop}() {
return this._data.get${types[propType].js}(${parseInt(offset)}, true);
}
/**
* ${comment}
* @param {${types[propType].js}} sets the value of ${prop}
*/
set ${prop}(v) {
return this._data.set${types[propType].js}(${parseInt(
offset
)}, v, true);
}`;
args += `${prop}: ${types[propType].js}, `;
typeDef += ` * @property {${types[propType].js}} ${propType} ${comment}\n`;
sqlData += `, ${prop} ${types[propType].sql}`;
cData += `
${types[propType].c} ${prop}; // ${comment}`;
break;
case "struct":
const jsSize = parseInt(offset) + parseInt(types[propType].size);
jsData += `
/**
* ${comment}
* @return {${types[propType].js}} gets the value of ${prop}
*/
get ${prop}() {
return new ${propType}({}, new Uint8Array(this._ptr.slice(${offset}, ${jsSize})));
}
/**
* ${comment}
* @param {${types[propType].js}} sets the value of ${prop}
*/
set ${prop}(v) {
this._ptr.set(v._ptr, ${offset});
}`;
const importS = `import ${propType} from "./${propType}"\n`;
if (!importStatements.includes(importS)) {
importStatements += importS;
}
const localKey = `${prop.toLowerCase()}_id`;
args += `${prop}: ${types[propType].js}, `;
typeDef += ` * @property {${types[propType].js}} ${propType} ${comment}\n`;
sqlData += `, ${localKey} INTEGER`;
foreignKeys += `\n, FOREIGN KEY(${localKey}) REFERENCES ${propType}(id)`
cData += `\n\t\t${types[propType].c} ${prop}; // ${comment}`;
break;
case "array":
throw new Error("Not Implemented!");
break;
default:
break;
}
size += parseInt(typeSize);
offset += parseInt(typeSize);
}
/**
* add the new type to the list so we can use it for structs later.
*/
types[type] = {
c: type,
js: type,
size: parseInt(size),
};
typeDef += " */\n"
args += `}}`
jsData = typeDef + jsData;
jsData += jsStructConstructor(size, containsString, type, args);
jsData += `\n}\n\nexport default ${type}`;
cData += `\n} ${type};\n\n`;
sqlData += `${foreignKeys});\n\n`;
await Bun.write(
Bun.file(values.javascript_out + type + ".js"),
importStatements + jsData
);
}
await Bun.write(Bun.file(values.c_out + "types.h"), cData);
await Bun.write(Bun.file(values.sqlite_out + "types.sql"), sqlData);