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);